diff --git a/FORECAST_VALIDATION_IMPLEMENTATION_SUMMARY.md b/docs/03-features/forecasting/validation-implementation.md similarity index 100% rename from FORECAST_VALIDATION_IMPLEMENTATION_SUMMARY.md rename to docs/03-features/forecasting/validation-implementation.md diff --git a/WHATSAPP_IMPLEMENTATION_SUMMARY.md b/docs/03-features/notifications/whatsapp/implementation-summary.md similarity index 100% rename from WHATSAPP_IMPLEMENTATION_SUMMARY.md rename to docs/03-features/notifications/whatsapp/implementation-summary.md diff --git a/WHATSAPP_MASTER_ACCOUNT_SETUP.md b/docs/03-features/notifications/whatsapp/master-account-setup.md similarity index 100% rename from WHATSAPP_MASTER_ACCOUNT_SETUP.md rename to docs/03-features/notifications/whatsapp/master-account-setup.md diff --git a/docs/MULTI_TENANT_WHATSAPP_IMPLEMENTATION_SUMMARY.md b/docs/03-features/notifications/whatsapp/multi-tenant-implementation.md similarity index 100% rename from docs/MULTI_TENANT_WHATSAPP_IMPLEMENTATION_SUMMARY.md rename to docs/03-features/notifications/whatsapp/multi-tenant-implementation.md diff --git a/WHATSAPP_SHARED_ACCOUNT_GUIDE.md b/docs/03-features/notifications/whatsapp/shared-account-guide.md similarity index 100% rename from WHATSAPP_SHARED_ACCOUNT_GUIDE.md rename to docs/03-features/notifications/whatsapp/shared-account-guide.md diff --git a/docs/poi-detection-system.md b/docs/03-features/specifications/poi-detection-system.md similarity index 100% rename from docs/poi-detection-system.md rename to docs/03-features/specifications/poi-detection-system.md diff --git a/docs/wizard-flow-specification.md b/docs/03-features/specifications/wizard-flow-specification.md similarity index 100% rename from docs/wizard-flow-specification.md rename to docs/03-features/specifications/wizard-flow-specification.md diff --git a/docs/COLIMA-SETUP.md b/docs/05-deployment/colima-setup.md similarity index 100% rename from docs/COLIMA-SETUP.md rename to docs/05-deployment/colima-setup.md diff --git a/docs/K8S-PRODUCTION-READINESS-SUMMARY.md b/docs/05-deployment/k8s-production-readiness.md similarity index 100% rename from docs/K8S-PRODUCTION-READINESS-SUMMARY.md rename to docs/05-deployment/k8s-production-readiness.md diff --git a/docs/VPS-SIZING-PRODUCTION.md b/docs/05-deployment/vps-sizing-production.md similarity index 100% rename from docs/VPS-SIZING-PRODUCTION.md rename to docs/05-deployment/vps-sizing-production.md diff --git a/docs/FRONTEND_CHANGES_NEEDED.md b/docs/FRONTEND_CHANGES_NEEDED.md deleted file mode 100644 index 7453b437..00000000 --- a/docs/FRONTEND_CHANGES_NEEDED.md +++ /dev/null @@ -1,131 +0,0 @@ -# Frontend Changes Needed for Notification Settings - -## File: frontend/src/pages/app/settings/bakery/BakerySettingsPage.tsx - -### 1. Update imports (line 3) -```typescript -import { Store, MapPin, Clock, Settings as SettingsIcon, Save, X, AlertCircle, Loader, Bell } from 'lucide-react'; -``` - -### 2. Add NotificationSettings to type imports (line 17) -```typescript -import type { - ProcurementSettings, - InventorySettings, - ProductionSettings, - SupplierSettings, - POSSettings, - OrderSettings, - NotificationSettings, // ADD THIS -} from '../../../../api/types/settings'; -``` - -### 3. Import NotificationSettingsCard component (after line 24) -```typescript -import NotificationSettingsCard from '../../database/ajustes/cards/NotificationSettingsCard'; -``` - -### 4. Add notification settings state (after line 100) -```typescript -const [notificationSettings, setNotificationSettings] = useState(null); -``` - -### 5. Load notification settings in useEffect (line 140, add this line) -```typescript -setNotificationSettings(settings.notification_settings); -``` - -### 6. Add notifications tab trigger (after line 389, before closing ) -```typescript - - - {t('bakery.tabs.notifications')} - -``` - -### 7. Add notifications tab content (after line 691, before ) -```typescript -{/* Tab 4: Notifications */} - -
- {notificationSettings && ( - { - setNotificationSettings(newSettings); - handleOperationalSettingsChange(); - }} - disabled={isLoading} - /> - )} -
-
-``` - -### 8. Update handleSaveOperationalSettings function (line 233) - -Change from: -```typescript -if (!tenantId || !procurementSettings || !inventorySettings || !productionSettings || - !supplierSettings || !posSettings || !orderSettings) { - return; -} -``` - -To: -```typescript -if (!tenantId || !procurementSettings || !inventorySettings || !productionSettings || - !supplierSettings || !posSettings || !orderSettings || !notificationSettings) { - return; -} -``` - -### 9. Add notification_settings to mutation (line 250) - -Add this line inside the mutation: -```typescript -await updateSettingsMutation.mutateAsync({ - tenantId, - updates: { - procurement_settings: procurementSettings, - inventory_settings: inventorySettings, - production_settings: productionSettings, - supplier_settings: supplierSettings, - pos_settings: posSettings, - order_settings: orderSettings, - notification_settings: notificationSettings, // ADD THIS - }, -}); -``` - -### 10. Update handleDiscard function (line 316) - -Add this line: -```typescript -setNotificationSettings(settings.notification_settings); -``` - -### 11. Update floating save button condition (line 717) - -Change: -```typescript -onClick={activeTab === 'operations' ? handleSaveOperationalSettings : handleSaveConfig} -``` - -To: -```typescript -onClick={activeTab === 'operations' || activeTab === 'notifications' ? handleSaveOperationalSettings : handleSaveConfig} -``` - -## Summary - -All translations have been added to: -- ✅ `/frontend/src/locales/es/ajustes.json` -- ✅ `/frontend/src/locales/eu/ajustes.json` -- ✅ `/frontend/src/locales/es/settings.json` -- ✅ `/frontend/src/locales/eu/settings.json` - -The NotificationSettingsCard component has been created at: -- ✅ `/frontend/src/pages/app/database/ajustes/cards/NotificationSettingsCard.tsx` - -You just need to apply the changes listed above to BakerySettingsPage.tsx to complete the frontend integration. diff --git a/docs/AUTO_TRIGGER_SUGGESTIONS_PHASE3.md b/docs/archive/implementation-summaries/auto-trigger-suggestions-phase3.md similarity index 100% rename from docs/AUTO_TRIGGER_SUGGESTIONS_PHASE3.md rename to docs/archive/implementation-summaries/auto-trigger-suggestions-phase3.md diff --git a/docs/AUTOMATIC_LOCATION_CONTEXT_IMPLEMENTATION.md b/docs/archive/implementation-summaries/automatic-location-context.md similarity index 100% rename from docs/AUTOMATIC_LOCATION_CONTEXT_IMPLEMENTATION.md rename to docs/archive/implementation-summaries/automatic-location-context.md diff --git a/docs/BAKERY_SETTINGS_PAGE_CHANGES.md b/docs/archive/implementation-summaries/bakery-settings-page-changes.md similarity index 100% rename from docs/BAKERY_SETTINGS_PAGE_CHANGES.md rename to docs/archive/implementation-summaries/bakery-settings-page-changes.md diff --git a/docs/COMPLETE_IMPLEMENTATION_SUMMARY.md b/docs/archive/implementation-summaries/complete-implementation-summary.md similarity index 100% rename from docs/COMPLETE_IMPLEMENTATION_SUMMARY.md rename to docs/archive/implementation-summaries/complete-implementation-summary.md diff --git a/docs/IMPLEMENTATION_COMPLETE.md b/docs/archive/implementation-summaries/implementation-complete.md similarity index 100% rename from docs/IMPLEMENTATION_COMPLETE.md rename to docs/archive/implementation-summaries/implementation-complete.md diff --git a/docs/LOCATION_CONTEXT_COMPLETE_SUMMARY.md b/docs/archive/implementation-summaries/location-context-complete.md similarity index 100% rename from docs/LOCATION_CONTEXT_COMPLETE_SUMMARY.md rename to docs/archive/implementation-summaries/location-context-complete.md diff --git a/docs/SERVICES-COMPLETED-SUMMARY.md b/docs/archive/implementation-summaries/services-completed-summary.md similarity index 100% rename from docs/SERVICES-COMPLETED-SUMMARY.md rename to docs/archive/implementation-summaries/services-completed-summary.md diff --git a/docs/SMART_CALENDAR_SUGGESTIONS_PHASE2.md b/docs/archive/implementation-summaries/smart-calendar-suggestions-phase2.md similarity index 100% rename from docs/SMART_CALENDAR_SUGGESTIONS_PHASE2.md rename to docs/archive/implementation-summaries/smart-calendar-suggestions-phase2.md diff --git a/frontend/src/api/hooks/newDashboard.ts b/frontend/src/api/hooks/newDashboard.ts index df99d8d3..3cf0edac 100644 --- a/frontend/src/api/hooks/newDashboard.ts +++ b/frontend/src/api/hooks/newDashboard.ts @@ -49,6 +49,8 @@ export interface ReasoningInputs { aiInsights: boolean; } +// Note: This is a different interface from PurchaseOrderSummary in purchase_orders.ts +// This is specifically for OrchestrationSummary dashboard display export interface PurchaseOrderSummary { supplierName: string; itemCategories: string[]; diff --git a/frontend/src/api/hooks/purchase-orders.ts b/frontend/src/api/hooks/purchase-orders.ts index bdc28bab..df7dc7d0 100644 --- a/frontend/src/api/hooks/purchase-orders.ts +++ b/frontend/src/api/hooks/purchase-orders.ts @@ -10,6 +10,7 @@ import type { PurchaseOrderDetail, PurchaseOrderSearchParams, PurchaseOrderUpdateData, + PurchaseOrderCreateData, PurchaseOrderStatus, CreateDeliveryInput, DeliveryResponse @@ -19,6 +20,7 @@ import { getPurchaseOrder, getPendingApprovalPurchaseOrders, getPurchaseOrdersByStatus, + createPurchaseOrder, updatePurchaseOrder, approvePurchaseOrder, rejectPurchaseOrder, @@ -112,6 +114,37 @@ export const usePurchaseOrder = ( }); }; +/** + * Hook to create a new purchase order + */ +export const useCreatePurchaseOrder = ( + options?: UseMutationOptions< + PurchaseOrderDetail, + ApiError, + { tenantId: string; data: PurchaseOrderCreateData } + > +) => { + const queryClient = useQueryClient(); + + return useMutation< + PurchaseOrderDetail, + ApiError, + { tenantId: string; data: PurchaseOrderCreateData } + >({ + mutationFn: ({ tenantId, data }) => createPurchaseOrder(tenantId, data), + onSuccess: (data, variables) => { + // Invalidate all lists to refresh with new PO + queryClient.invalidateQueries({ queryKey: purchaseOrderKeys.lists() }); + // Add to cache + queryClient.setQueryData( + purchaseOrderKeys.detail(variables.tenantId, data.id), + data + ); + }, + ...options, + }); +}; + /** * Hook to update a purchase order */ diff --git a/frontend/src/api/hooks/suppliers.ts b/frontend/src/api/hooks/suppliers.ts index 0d4b887e..b23f9358 100644 --- a/frontend/src/api/hooks/suppliers.ts +++ b/frontend/src/api/hooks/suppliers.ts @@ -15,11 +15,6 @@ import type { SupplierSearchParams, SupplierStatistics, SupplierDeletionSummary, - PurchaseOrderCreate, - PurchaseOrderUpdate, - PurchaseOrderResponse, - PurchaseOrderApproval, - PurchaseOrderSearchParams, DeliveryCreate, DeliveryUpdate, DeliveryResponse, @@ -49,15 +44,6 @@ export const suppliersKeys = { byType: (tenantId: string, supplierType: string) => [...suppliersKeys.suppliers.all(), 'by-type', tenantId, supplierType] as const, }, - purchaseOrders: { - all: () => [...suppliersKeys.all, 'purchase-orders'] as const, - lists: () => [...suppliersKeys.purchaseOrders.all(), 'list'] as const, - list: (params?: PurchaseOrderSearchParams) => - [...suppliersKeys.purchaseOrders.lists(), params] as const, - details: () => [...suppliersKeys.purchaseOrders.all(), 'detail'] as const, - detail: (orderId: string) => - [...suppliersKeys.purchaseOrders.details(), orderId] as const, - }, deliveries: { all: () => [...suppliersKeys.all, 'deliveries'] as const, lists: () => [...suppliersKeys.deliveries.all(), 'list'] as const, @@ -173,35 +159,6 @@ export const useSuppliersByType = ( }); }; -// Purchase Order Queries -export const usePurchaseOrders = ( - tenantId: string, - queryParams?: PurchaseOrderSearchParams, - options?: Omit, 'queryKey' | 'queryFn'> -) => { - return useQuery({ - queryKey: suppliersKeys.purchaseOrders.list(queryParams), - queryFn: () => suppliersService.getPurchaseOrders(tenantId, queryParams as any), - enabled: !!tenantId, - staleTime: 1 * 60 * 1000, // 1 minute - ...options, - }); -}; - -export const usePurchaseOrder = ( - tenantId: string, - orderId: string, - options?: Omit, 'queryKey' | 'queryFn'> -) => { - return useQuery({ - queryKey: suppliersKeys.purchaseOrders.detail(orderId), - queryFn: () => suppliersService.getPurchaseOrder(tenantId, orderId), - enabled: !!tenantId && !!orderId, - staleTime: 2 * 60 * 1000, // 2 minutes - ...options, - }); -}; - // Delivery Queries export const useDeliveries = ( tenantId: string, @@ -462,94 +419,6 @@ export const useHardDeleteSupplier = ( }); }; -// Purchase Order Mutations -export const useCreatePurchaseOrder = ( - options?: UseMutationOptions -) => { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: (orderData) => suppliersService.createPurchaseOrder(orderData), - onSuccess: (data) => { - // Add to cache - queryClient.setQueryData( - suppliersKeys.purchaseOrders.detail(data.id), - data - ); - - // Invalidate lists - queryClient.invalidateQueries({ - queryKey: suppliersKeys.purchaseOrders.lists() - }); - }, - ...options, - }); -}; - -export const useUpdatePurchaseOrder = ( - options?: UseMutationOptions< - PurchaseOrderResponse, - ApiError, - { orderId: string; updateData: PurchaseOrderUpdate } - > -) => { - const queryClient = useQueryClient(); - - return useMutation< - PurchaseOrderResponse, - ApiError, - { orderId: string; updateData: PurchaseOrderUpdate } - >({ - mutationFn: ({ orderId, updateData }) => - suppliersService.updatePurchaseOrder(orderId, updateData), - onSuccess: (data, { orderId }) => { - // Update cache - queryClient.setQueryData( - suppliersKeys.purchaseOrders.detail(orderId), - data - ); - - // Invalidate lists - queryClient.invalidateQueries({ - queryKey: suppliersKeys.purchaseOrders.lists() - }); - }, - ...options, - }); -}; - -export const useApprovePurchaseOrder = ( - options?: UseMutationOptions< - PurchaseOrderResponse, - ApiError, - { orderId: string; approval: PurchaseOrderApproval } - > -) => { - const queryClient = useQueryClient(); - - return useMutation< - PurchaseOrderResponse, - ApiError, - { orderId: string; approval: PurchaseOrderApproval } - >({ - mutationFn: ({ orderId, approval }) => - suppliersService.approvePurchaseOrder(orderId, approval), - onSuccess: (data, { orderId }) => { - // Update cache - queryClient.setQueryData( - suppliersKeys.purchaseOrders.detail(orderId), - data - ); - - // Invalidate lists - queryClient.invalidateQueries({ - queryKey: suppliersKeys.purchaseOrders.lists() - }); - }, - ...options, - }); -}; - // Delivery Mutations export const useCreateDelivery = ( options?: UseMutationOptions diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index 2a853726..dc9bf9ab 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -593,8 +593,6 @@ export { useTopSuppliers, usePendingApprovalSuppliers, useSuppliersByType, - usePurchaseOrders, - usePurchaseOrder, useDeliveries, useDelivery, useSupplierPerformanceMetrics, @@ -603,9 +601,6 @@ export { useUpdateSupplier, useDeleteSupplier, useApproveSupplier, - useCreatePurchaseOrder, - useUpdatePurchaseOrder, - useApprovePurchaseOrder, useCreateDelivery, useUpdateDelivery, useConfirmDeliveryReceipt, diff --git a/frontend/src/api/services/purchase_orders.ts b/frontend/src/api/services/purchase_orders.ts index 00616b4b..6645c857 100644 --- a/frontend/src/api/services/purchase_orders.ts +++ b/frontend/src/api/services/purchase_orders.ts @@ -123,6 +123,40 @@ export interface PurchaseOrderUpdateData { internal_notes?: string; } +export interface PurchaseOrderItemCreate { + inventory_product_id: string; + ordered_quantity: number; + unit_price: string; // Decimal as string + unit_of_measure: string; + quality_requirements?: string; + item_notes?: string; +} + +export interface PurchaseOrderCreateData { + supplier_id: string; + required_delivery_date?: string; + priority?: PurchaseOrderPriority; + tax_amount?: number; + shipping_cost?: number; + discount_amount?: number; + notes?: string; + procurement_plan_id?: string; + items: PurchaseOrderItemCreate[]; +} + +/** + * Create a new purchase order + */ +export async function createPurchaseOrder( + tenantId: string, + data: PurchaseOrderCreateData +): Promise { + return apiClient.post( + `/tenants/${tenantId}/procurement/purchase-orders`, + data + ); +} + /** * Get list of purchase orders with optional filters */ diff --git a/frontend/src/api/services/suppliers.ts b/frontend/src/api/services/suppliers.ts index 38ee5b46..25756a29 100644 --- a/frontend/src/api/services/suppliers.ts +++ b/frontend/src/api/services/suppliers.ts @@ -24,11 +24,6 @@ import type { SupplierStatistics, SupplierDeletionSummary, SupplierResponse as SupplierResponse_, - PurchaseOrderCreate, - PurchaseOrderUpdate, - PurchaseOrderResponse, - PurchaseOrderApproval, - PurchaseOrderSearchParams, DeliveryCreate, DeliveryUpdate, DeliveryResponse, diff --git a/frontend/src/api/types/suppliers.ts b/frontend/src/api/types/suppliers.ts index dcc016f9..c9f726d5 100644 --- a/frontend/src/api/types/suppliers.ts +++ b/frontend/src/api/types/suppliers.ts @@ -367,173 +367,6 @@ export interface SupplierSummary { created_at: string; } -// ===== PURCHASE ORDER SCHEMAS ===== -// Mirror: PurchaseOrderItemCreate from suppliers.py:187 - -export interface PurchaseOrderItemCreate { - inventory_product_id: string; - product_code?: string | null; // max_length=100 - ordered_quantity: number; // gt=0 - unit_of_measure: string; // max_length=20 - unit_price: number; // gt=0 - quality_requirements?: string | null; - item_notes?: string | null; -} - -// Mirror: PurchaseOrderItemUpdate from suppliers.py:198 -export interface PurchaseOrderItemUpdate { - ordered_quantity?: number | null; // gt=0 - unit_price?: number | null; // gt=0 - quality_requirements?: string | null; - item_notes?: string | null; -} - -// Mirror: PurchaseOrderItemResponse from suppliers.py (inferred) -export interface PurchaseOrderItemResponse { - id: string; - tenant_id: string; - purchase_order_id: string; - inventory_product_id: string; - product_code: string | null; - price_list_item_id: string | null; - ordered_quantity: number; - unit_of_measure: string; - unit_price: number; - line_total: number; - received_quantity: number; - remaining_quantity: number; - quality_requirements: string | null; - item_notes: string | null; - created_at: string; - updated_at: string; -} - -// Mirror: PurchaseOrderCreate from suppliers.py (inferred) -export interface PurchaseOrderCreate { - supplier_id: string; - items: PurchaseOrderItemCreate[]; // min_items=1 - - // Order details - reference_number?: string | null; // max_length=100 - priority?: string; // Default: "normal", max_length=20 - required_delivery_date?: string | null; - - // Delivery info - delivery_address?: string | null; - delivery_instructions?: string | null; - delivery_contact?: string | null; // max_length=200 - delivery_phone?: string | null; // max_length=30 - - // Financial (all default=0, ge=0) - tax_amount?: number; - shipping_cost?: number; - discount_amount?: number; - - // Additional - notes?: string | null; - internal_notes?: string | null; - terms_and_conditions?: string | null; -} - -// Mirror: PurchaseOrderUpdate from suppliers.py (inferred) -export interface PurchaseOrderUpdate { - reference_number?: string | null; - priority?: string | null; - required_delivery_date?: string | null; - estimated_delivery_date?: string | null; - supplier_reference?: string | null; // max_length=100 - delivery_address?: string | null; - delivery_instructions?: string | null; - delivery_contact?: string | null; - delivery_phone?: string | null; - tax_amount?: number | null; - shipping_cost?: number | null; - discount_amount?: number | null; - notes?: string | null; - internal_notes?: string | null; - terms_and_conditions?: string | null; -} - -// Mirror: PurchaseOrderStatusUpdate from suppliers.py (inferred) -export interface PurchaseOrderStatusUpdate { - status: PurchaseOrderStatus; - notes?: string | null; -} - -// Mirror: PurchaseOrderApproval from suppliers.py (inferred) -export interface PurchaseOrderApproval { - action: 'approve' | 'reject'; - notes?: string | null; -} - -// Mirror: PurchaseOrderResponse from suppliers.py (inferred) -export interface PurchaseOrderResponse { - id: string; - tenant_id: string; - supplier_id: string; - po_number: string; - status: PurchaseOrderStatus; - order_date: string; - reference_number: string | null; - priority: string; - required_delivery_date: string | null; - estimated_delivery_date: string | null; - - // Financial - subtotal: number; - tax_amount: number; - shipping_cost: number; - discount_amount: number; - total_amount: number; - currency: string; - - // Delivery - delivery_address: string | null; - delivery_instructions: string | null; - delivery_contact: string | null; - delivery_phone: string | null; - - // Approval - requires_approval: boolean; - approved_by: string | null; - approved_at: string | null; - rejection_reason: string | null; - - // Communication - sent_to_supplier_at: string | null; - supplier_confirmation_date: string | null; - supplier_reference: string | null; - - // Additional - notes: string | null; - internal_notes: string | null; - terms_and_conditions: string | null; - - // Audit - created_at: string; - updated_at: string; - created_by: string; - updated_by: string; - - // Related data - supplier?: SupplierSummary | null; - items?: PurchaseOrderItemResponse[] | null; -} - -// Mirror: PurchaseOrderSummary from suppliers.py (inferred) -export interface PurchaseOrderSummary { - id: string; - po_number: string; - supplier_id: string; - supplier_name: string | null; - status: PurchaseOrderStatus; - priority: string; - order_date: string; - required_delivery_date: string | null; - total_amount: number; - currency: string; - created_at: string; -} // ===== DELIVERY SCHEMAS ===== // Mirror: DeliveryItemCreate from suppliers.py (inferred) @@ -828,15 +661,6 @@ export interface SupplierStatistics { total_spend: number; } -export interface PurchaseOrderStatistics { - total_orders: number; - status_counts: Record; - this_month_orders: number; - this_month_spend: number; - avg_order_value: number; - overdue_count: number; - pending_approval: number; -} export interface DeliveryPerformanceStats { total_deliveries: number; @@ -934,16 +758,8 @@ export interface SupplierSearchParams { offset?: number; // Default: 0, ge=0 } -export interface PurchaseOrderSearchParams { - supplier_id?: string | null; - status?: PurchaseOrderStatus | null; - priority?: string | null; - date_from?: string | null; - date_to?: string | null; - search_term?: string | null; - limit?: number; - offset?: number; -} +// ⚠️ DEPRECATED: Use PurchaseOrderSearchParams from '@/api/services/purchase_orders' instead +// Duplicate definition - use the one from purchase_orders service export interface DeliverySearchParams { supplier_id?: string | null; diff --git a/frontend/src/components/dashboard/PurchaseOrderDetailsModal.tsx b/frontend/src/components/dashboard/PurchaseOrderDetailsModal.tsx index 697cb9d3..92e2a251 100644 --- a/frontend/src/components/dashboard/PurchaseOrderDetailsModal.tsx +++ b/frontend/src/components/dashboard/PurchaseOrderDetailsModal.tsx @@ -3,26 +3,25 @@ // ================================================================ /** * Purchase Order Details Modal - * Quick view of PO details from the Action Queue + * Unified view/edit modal for PO details from the Action Queue + * Now using EditViewModal with proper API response structure */ -import React from 'react'; +import React, { useState, useMemo } from 'react'; import { - X, Package, Building2, Calendar, - Truck, Euro, FileText, CheckCircle, - Clock, - AlertCircle, + Edit, } from 'lucide-react'; import { useTranslation } from 'react-i18next'; -import { usePurchaseOrder } from '../../api/hooks/purchase-orders'; -import { formatDistanceToNow } from 'date-fns'; -import { es, enUS, eu as euLocale } from 'date-fns/locale'; +import { usePurchaseOrder, useUpdatePurchaseOrder } from '../../api/hooks/purchase-orders'; +import { useUserById } from '../../api/hooks/user'; +import { EditViewModal, EditViewModalSection } from '../ui/EditViewModal/EditViewModal'; +import type { PurchaseOrderItem } from '../../api/services/purchase_orders'; interface PurchaseOrderDetailsModalProps { poId: string; @@ -30,380 +29,439 @@ interface PurchaseOrderDetailsModalProps { isOpen: boolean; onClose: () => void; onApprove?: (poId: string) => void; - onModify?: (poId: string) => void; + initialMode?: 'view' | 'edit'; } -const localeMap = { - es: es, - en: enUS, - eu: euLocale, -}; - export const PurchaseOrderDetailsModal: React.FC = ({ poId, tenantId, isOpen, onClose, onApprove, - onModify, + initialMode = 'view', }) => { - const { t, i18n } = useTranslation(); - const { data: po, isLoading } = usePurchaseOrder(tenantId, poId); + const { t, i18n } = useTranslation(['purchase_orders', 'common']); + const { data: po, isLoading, refetch } = usePurchaseOrder(tenantId, poId); + const [mode, setMode] = useState<'view' | 'edit'>(initialMode); + const updatePurchaseOrderMutation = useUpdatePurchaseOrder(); - if (!isOpen) return null; + // Component to display user name with data fetching + const UserName: React.FC<{ userId: string | undefined | null }> = ({ userId }) => { + if (!userId) return <>{t('common:not_available')}; - const dateLocale = localeMap[i18n.language as keyof typeof localeMap] || enUS; + if (userId === '00000000-0000-0000-0000-000000000001' || userId === '00000000-0000-0000-0000-000000000000') { + return <>{t('common:system')}; + } - // Format currency - const formatCurrency = (value: any) => { - const num = Number(value); - return isNaN(num) ? '0.00' : num.toFixed(2); + const { data: user, isLoading } = useUserById(userId, { + retry: 1, + staleTime: 10 * 60 * 1000, + }); + + if (isLoading) return <>{t('common:loading')}; + if (!user) return <>{t('common:unknown_user')}; + + return <>{user.full_name || user.email || t('common:user')}; }; - const getStatusBadge = (status: string) => { - const statusConfig = { - draft: { - bg: 'var(--color-gray-100)', - text: 'var(--color-gray-700)', - label: t('purchase_orders:status.draft'), - }, - pending_approval: { - bg: 'var(--color-warning-100)', - text: 'var(--color-warning-700)', - label: t('purchase_orders:status.pending_approval'), - }, - approved: { - bg: 'var(--color-success-100)', - text: 'var(--color-success-700)', - label: t('purchase_orders:status.approved'), - }, - sent: { - bg: 'var(--color-info-100)', - text: 'var(--color-info-700)', - label: t('purchase_orders:status.sent'), - }, - partially_received: { - bg: 'var(--color-warning-100)', - text: 'var(--color-warning-700)', - label: t('purchase_orders:status.partially_received'), - }, - received: { - bg: 'var(--color-success-100)', - text: 'var(--color-success-700)', - label: t('purchase_orders:status.received'), - }, - cancelled: { - bg: 'var(--color-error-100)', - text: 'var(--color-error-700)', - label: t('purchase_orders:status.cancelled'), - }, - }; + // Component to display PO items + const PurchaseOrderItemsTable: React.FC<{ items: PurchaseOrderItem[] }> = ({ items }) => { + if (!items || items.length === 0) { + return ( +
+ +

{t('no_items')}

+
+ ); + } - const config = statusConfig[status as keyof typeof statusConfig] || statusConfig.draft; + const totalAmount = items.reduce((sum, item) => { + const price = parseFloat(item.unit_price) || 0; + const quantity = item.ordered_quantity || 0; + return sum + (price * quantity); + }, 0); return ( - - {config.label} - +
+ {items.map((item: PurchaseOrderItem, index: number) => { + const unitPrice = parseFloat(item.unit_price) || 0; + const quantity = item.ordered_quantity || 0; + const itemTotal = unitPrice * quantity; + const productName = item.product_name || `${t('product')} ${index + 1}`; + + return ( +
+
+
+

{productName}

+ {item.product_code && ( +

+ {t('sku')}: {item.product_code} +

+ )} +
+
+

+ €{itemTotal.toFixed(2)} +

+
+
+
+
+

{t('quantity')}

+

+ {quantity} {item.unit_of_measure} +

+
+
+

{t('unit_price')}

+

€{unitPrice.toFixed(2)}

+
+
+ {item.quality_requirements && ( +
+

{t('quality_requirements')}

+

{item.quality_requirements}

+
+ )} + {item.item_notes && ( +
+

{t('common:notes')}

+

{item.item_notes}

+
+ )} +
+ ); + })} +
+ {t('total')} + €{totalAmount.toFixed(2)} +
+
); }; + // Priority and unit options for edit mode + const priorityOptions = [ + { value: 'urgent', label: t('priority_urgent') }, + { value: 'high', label: t('priority_high') }, + { value: 'normal', label: t('priority_normal') }, + { value: 'low', label: t('priority_low') } + ]; + + const unitOptions = [ + { value: 'kg', label: t('unit_kg') }, + { value: 'g', label: t('unit_g') }, + { value: 'l', label: t('unit_l') }, + { value: 'ml', label: t('unit_ml') }, + { value: 'units', label: t('unit_units') }, + { value: 'boxes', label: t('unit_boxes') }, + { value: 'bags', label: t('unit_bags') } + ]; + + // Build sections for EditViewModal + const buildViewSections = (): EditViewModalSection[] => { + if (!po) return []; + + const formatCurrency = (value: any) => { + const num = Number(value); + return isNaN(num) ? '0.00' : num.toFixed(2); + }; + + const sections: EditViewModalSection[] = [ + { + title: t('general_information'), + icon: FileText, + fields: [ + { + label: t('po_number'), + value: po.po_number, + type: 'text' as const + }, + { + label: t('status_label'), + value: t(`status.${po.status}`), + type: 'status' as const + }, + { + label: t('priority'), + value: t(`priority_${po.priority}` as any) || po.priority, + type: 'text' as const + }, + { + label: t('created'), + value: new Date(po.created_at).toLocaleDateString(i18n.language, { + year: 'numeric', + month: 'long', + day: 'numeric', + hour: '2-digit', + minute: '2-digit' + }), + type: 'text' as const + } + ] + }, + { + title: t('supplier_info'), + icon: Building2, + fields: [ + { + label: t('supplier_name'), + value: po.supplier?.name || t('common:unknown'), + type: 'text' as const + } + ] + }, + { + title: t('financial_summary'), + icon: Euro, + fields: [ + { + label: t('total_amount'), + value: `€${formatCurrency(po.total_amount)}`, + type: 'text' as const, + highlight: true + } + ] + }, + { + title: t('items'), + icon: Package, + fields: [ + { + label: '', + value: , + type: 'component' as const, + span: 2 + } + ] + }, + { + title: t('dates'), + icon: Calendar, + fields: [ + { + label: t('order_date'), + value: new Date(po.order_date).toLocaleDateString(i18n.language, { + year: 'numeric', + month: 'short', + day: 'numeric' + }), + type: 'text' as const + }, + ...(po.required_delivery_date ? [{ + label: t('required_delivery_date'), + value: new Date(po.required_delivery_date).toLocaleDateString(i18n.language, { + year: 'numeric', + month: 'short', + day: 'numeric' + }), + type: 'text' as const + }] : []), + ...(po.estimated_delivery_date ? [{ + label: t('expected_delivery'), + value: new Date(po.estimated_delivery_date).toLocaleDateString(i18n.language, { + year: 'numeric', + month: 'short', + day: 'numeric' + }), + type: 'text' as const + }] : []) + ] + } + ]; + + // Add notes section if present + if (po.notes) { + sections.push({ + title: t('notes'), + icon: FileText, + fields: [ + { + label: t('order_notes'), + value: po.notes, + type: 'text' as const + } + ] + }); + } + + return sections; + }; + + // Build sections for edit mode + const buildEditSections = (): EditViewModalSection[] => { + if (!po) return []; + + return [ + { + title: t('supplier_info'), + icon: Building2, + fields: [ + { + label: t('supplier'), + value: po.supplier?.name || t('common:unknown'), + type: 'text' as const, + editable: false, + helpText: t('supplier_cannot_modify') + } + ] + }, + { + title: t('order_details'), + icon: Calendar, + fields: [ + { + label: t('priority'), + value: po.priority, + type: 'select' as const, + editable: true, + options: priorityOptions, + helpText: t('adjust_priority') + }, + { + label: t('required_delivery_date'), + value: po.required_delivery_date || '', + type: 'date' as const, + editable: true, + helpText: t('delivery_deadline') + }, + { + label: t('notes'), + value: po.notes || '', + type: 'textarea' as const, + editable: true, + placeholder: t('special_instructions'), + span: 2, + helpText: t('additional_info') + } + ] + }, + { + title: t('products'), + icon: Package, + fields: [ + { + label: t('products'), + value: (po.items || []).map((item: PurchaseOrderItem) => ({ + id: item.id, + inventory_product_id: item.inventory_product_id, + product_code: item.product_code || '', + product_name: item.product_name || '', + ordered_quantity: item.ordered_quantity, + unit_of_measure: item.unit_of_measure, + unit_price: parseFloat(item.unit_price), + })), + type: 'list' as const, + editable: true, + span: 2, + helpText: t('modify_quantities') + } + ] + } + ]; + }; + + // Save handler for edit mode + const handleSave = async (formData: Record) => { + try { + const items = formData.items || []; + + if (items.length === 0) { + throw new Error(t('at_least_one_product')); + } + + // Validate quantities + const invalidQuantities = items.some((item: any) => item.ordered_quantity <= 0); + if (invalidQuantities) { + throw new Error(t('quantities_greater_zero')); + } + + // Validate required fields + const invalidProducts = items.some((item: any) => !item.product_name); + if (invalidProducts) { + throw new Error(t('products_need_names')); + } + + // Prepare the update data + const updateData: any = { + notes: formData.notes || undefined, + priority: formData.priority || undefined, + }; + + // Add delivery date if changed + if (formData.required_delivery_date) { + updateData.required_delivery_date = formData.required_delivery_date; + } + + // Update purchase order + await updatePurchaseOrderMutation.mutateAsync({ + tenantId, + poId, + data: updateData + }); + + // Refetch data and switch back to view mode + await refetch(); + setMode('view'); + } catch (error) { + console.error('Error modifying purchase order:', error); + throw error; + } + }; + + // Build actions for modal footer - only Approve button for pending approval POs + const buildActions = () => { + if (!po) return undefined; + + // Only show Approve button in view mode for pending approval POs + if (mode === 'view' && po.status === 'pending_approval') { + return [ + { + label: t('actions.approve'), + icon: CheckCircle, + onClick: () => { + onApprove?.(poId); + onClose(); + }, + variant: 'primary' as const + } + ]; + } + + return undefined; + }; + + const sections = mode === 'view' ? buildViewSections() : buildEditSections(); + return ( -
{ + setMode('view'); + onClose(); }} - > -
e.stopPropagation()} - style={{ - backgroundColor: 'var(--bg-primary)', - animation: 'slideUp 0.3s ease-out' - }} - > - - {/* Header */} -
-
-
- -
-
-

- {isLoading ? t('common:loading') : po?.po_number || t('purchase_orders:purchase_order')} -

- {po && ( -

- - {t('purchase_orders:created')} {formatDistanceToNow(new Date(po.created_at), { addSuffix: true, locale: dateLocale })} -

- )} -
-
- -
- - {/* Content */} -
- {isLoading ? ( -
-
-
- ) : po ? ( -
- {/* Status and Key Info */} -
- {getStatusBadge(po.status)} -
-

- {t('purchase_orders:total_amount')} -

-
- - - {formatCurrency(po.total_amount)} - -
-
-
- - {/* Supplier Info */} -
-
-
- -
-

- {t('purchase_orders:supplier')} -

-
-

- {po.supplier_name || t('common:unknown')} -

-
- - {/* Dates */} -
-
-
-
- -
-

- {t('purchase_orders:order_date')} -

-
-

- {new Date(po.order_date).toLocaleDateString(i18n.language, { - year: 'numeric', - month: 'short', - day: 'numeric' - })} -

-
- - {po.expected_delivery_date && ( -
-
-
- -
-

- {t('purchase_orders:expected_delivery')} -

-
-

- {new Date(po.expected_delivery_date).toLocaleDateString(i18n.language, { - year: 'numeric', - month: 'short', - day: 'numeric' - })} -

-
- )} -
- - {/* Items */} -
-
-
- -
-

- {t('purchase_orders:items')} -

- {po.items && po.items.length > 0 && ( - - {po.items.length} {po.items.length === 1 ? t('common:item') : t('common:items')} - - )} -
-
- {po.items && po.items.length > 0 ? ( - po.items.map((item: any, index: number) => ( -
-
-

- {item.ingredient_name || item.product_name} -

-

- {item.quantity} {item.unit} - × - €{formatCurrency(item.unit_price)} -

-
-

- €{formatCurrency(item.subtotal)} -

-
- )) - ) : ( -
- -

{t('purchase_orders:no_items')}

-
- )} -
-
- - {/* Notes */} - {po.notes && ( -
-
-
- -
-

- {t('purchase_orders:notes')} -

-
-

{po.notes}

-
- )} -
- ) : ( -
-

{t('purchase_orders:not_found')}

-
- )} -
- - {/* Footer Actions */} - {po && po.status === 'pending_approval' && ( -
- - -
- )} -
-
+ mode={mode} + onModeChange={setMode} + title={po?.po_number || t('purchase_order')} + subtitle={po ? new Date(po.created_at).toLocaleDateString(i18n.language, { + year: 'numeric', + month: 'long', + day: 'numeric' + }) : undefined} + sections={sections} + actions={buildActions()} + isLoading={isLoading} + size="lg" + // Enable edit mode via standard Edit button (only for pending approval) + onEdit={po?.status === 'pending_approval' ? () => setMode('edit') : undefined} + onSave={mode === 'edit' ? handleSave : undefined} + onCancel={mode === 'edit' ? () => setMode('view') : undefined} + saveLabel={t('actions.save')} + cancelLabel={t('actions.cancel')} + /> ); }; diff --git a/frontend/src/components/domain/procurement/CreatePurchaseOrderModal.tsx b/frontend/src/components/domain/procurement/CreatePurchaseOrderModal.tsx index d3f37e80..14123846 100644 --- a/frontend/src/components/domain/procurement/CreatePurchaseOrderModal.tsx +++ b/frontend/src/components/domain/procurement/CreatePurchaseOrderModal.tsx @@ -2,11 +2,12 @@ import React, { useState, useEffect, useMemo } from 'react'; import { Plus, Package, Calendar, Building2 } from 'lucide-react'; import { AddModal } from '../../ui/AddModal/AddModal'; import { useSuppliers } from '../../../api/hooks/suppliers'; -import { useCreatePurchaseOrder } from '../../../api/hooks/suppliers'; +import { useCreatePurchaseOrder } from '../../../api/hooks/purchase-orders'; import { useIngredients } from '../../../api/hooks/inventory'; import { useTenantStore } from '../../../stores/tenant.store'; import { suppliersService } from '../../../api/services/suppliers'; -import type { ProcurementRequirementResponse, PurchaseOrderItem } from '../../../api/types/orders'; +import type { ProcurementRequirementResponse } from '../../../api/types/orders'; +import type { PurchaseOrderItemCreate } from '../../../api/services/purchase_orders'; import type { SupplierSummary } from '../../../api/types/suppliers'; import type { IngredientResponse } from '../../../api/types/inventory'; import { statusColors } from '../../../styles/colors'; @@ -127,7 +128,7 @@ export const CreatePurchaseOrderModal: React.FC = setLoading(true); try { - let items: PurchaseOrderItem[] = []; + let items: PurchaseOrderItemCreate[] = []; if (requirements && requirements.length > 0) { // Create items from requirements list @@ -149,13 +150,11 @@ export const CreatePurchaseOrderModal: React.FC = const originalReq = requirements.find(req => req.id === item.id); return { inventory_product_id: originalReq?.product_id || '', - product_code: item.product_sku || '', - product_name: item.product_name, ordered_quantity: item.quantity, unit_of_measure: item.unit_of_measure, - unit_price: item.unit_price, + unit_price: String(item.unit_price || 0), // ✅ Convert to string for Decimal quality_requirements: originalReq?.quality_specifications ? JSON.stringify(originalReq.quality_specifications) : undefined, - notes: originalReq?.special_requirements || undefined + item_notes: originalReq?.special_requirements || undefined }; }); } else { @@ -180,29 +179,27 @@ export const CreatePurchaseOrderModal: React.FC = // Prepare purchase order items from manual entries with ingredient data items = manualProducts.map((item: any) => { - // Find the selected ingredient data - const selectedIngredient = ingredientsData.find(ing => ing.id === item.ingredient_id); - return { inventory_product_id: item.ingredient_id, - product_code: selectedIngredient?.sku || '', - product_name: selectedIngredient?.name || 'Ingrediente desconocido', ordered_quantity: item.quantity, unit_of_measure: item.unit_of_measure, - unit_price: item.unit_price, + unit_price: String(item.unit_price || 0), // ✅ Convert to string for Decimal quality_requirements: undefined, - notes: undefined + item_notes: undefined }; }); } - // Create purchase order + // Create purchase order using procurement service await createPurchaseOrderMutation.mutateAsync({ - supplier_id: formData.supplier_id, - priority: 'normal', - required_delivery_date: formData.delivery_date || undefined, - notes: formData.notes || undefined, - items + tenantId, + data: { + supplier_id: formData.supplier_id, + priority: 'normal', + required_delivery_date: formData.delivery_date || undefined, + notes: formData.notes || undefined, + items + } }); // Purchase order created successfully diff --git a/frontend/src/locales/en/purchase_orders.json b/frontend/src/locales/en/purchase_orders.json index d363f059..47368959 100644 --- a/frontend/src/locales/en/purchase_orders.json +++ b/frontend/src/locales/en/purchase_orders.json @@ -3,13 +3,57 @@ "purchase_orders": "Purchase Orders", "created": "Created", "supplier": "Supplier", + "supplier_name": "Supplier Name", "order_date": "Order Date", "expected_delivery": "Expected Delivery", "items": "Items", "no_items": "No items in this order", "notes": "Notes", + "order_notes": "Order Notes", "not_found": "Purchase order not found", "total_amount": "Total Amount", + "general_information": "General Information", + "financial_summary": "Financial Summary", + "dates": "Dates", + "po_number": "PO Number", + "status_label": "Status", + "quantity": "Quantity", + "unit_price": "Unit Price", + "quality_requirements": "Quality Requirements", + "total": "Total", + "priority": "Priority", + "required_delivery_date": "Required Delivery Date", + "supplier_info": "Supplier Information", + "order_details": "Order Details", + "products": "Products", + "modify_order": "Modify Purchase Order", + "modifying_order": "Modifying Order", + "supplier_cannot_modify": "Supplier cannot be modified", + "adjust_priority": "Adjust the priority of this order", + "delivery_deadline": "Delivery deadline", + "special_instructions": "Special instructions for supplier...", + "additional_info": "Additional information or special instructions", + "product": "Product", + "sku": "SKU", + "ordered_quantity": "Ordered Quantity", + "unit": "Unit", + "unit_price_euro": "Unit Price (€)", + "add_product": "Add Product", + "modify_quantities": "Modify quantities, units and prices as needed", + "at_least_one_product": "Please add at least one product", + "quantities_greater_zero": "All quantities must be greater than 0", + "products_need_names": "All products must have a name", + "priority_urgent": "Urgent", + "priority_high": "High", + "priority_normal": "Normal", + "priority_low": "Low", + "unit_kg": "Kilograms", + "unit_g": "Grams", + "unit_l": "Liters", + "unit_ml": "Milliliters", + "unit_units": "Units", + "unit_boxes": "Boxes", + "unit_bags": "Bags", "status": { "draft": "Draft", "pending_approval": "Pending Approval", @@ -31,6 +75,8 @@ "actions": { "approve": "Approve Order", "modify": "Modify Order", - "close": "Close" + "close": "Close", + "save": "Save Changes", + "cancel": "Cancel" } } diff --git a/frontend/src/locales/es/purchase_orders.json b/frontend/src/locales/es/purchase_orders.json index c9abd61b..8ff60199 100644 --- a/frontend/src/locales/es/purchase_orders.json +++ b/frontend/src/locales/es/purchase_orders.json @@ -3,13 +3,57 @@ "purchase_orders": "Órdenes de Compra", "created": "Creada", "supplier": "Proveedor", + "supplier_name": "Nombre del Proveedor", "order_date": "Fecha de Pedido", "expected_delivery": "Entrega Esperada", "items": "Artículos", "no_items": "No hay artículos en esta orden", "notes": "Notas", + "order_notes": "Notas de la Orden", "not_found": "Orden de compra no encontrada", "total_amount": "Monto Total", + "general_information": "Información General", + "financial_summary": "Resumen Financiero", + "dates": "Fechas", + "po_number": "Número de Orden", + "status_label": "Estado", + "quantity": "Cantidad", + "unit_price": "Precio Unitario", + "quality_requirements": "Requisitos de Calidad", + "total": "Total", + "priority": "Prioridad", + "required_delivery_date": "Fecha de Entrega Requerida", + "supplier_info": "Información del Proveedor", + "order_details": "Detalles de la Orden", + "products": "Productos", + "modify_order": "Modificar Orden de Compra", + "modifying_order": "Modificando Orden", + "supplier_cannot_modify": "El proveedor no puede ser modificado", + "adjust_priority": "Ajusta la prioridad de esta orden", + "delivery_deadline": "Fecha límite para la entrega", + "special_instructions": "Instrucciones especiales para el proveedor...", + "additional_info": "Información adicional o instrucciones especiales", + "product": "Producto", + "sku": "SKU", + "ordered_quantity": "Cantidad Pedida", + "unit": "Unidad", + "unit_price_euro": "Precio Unitario (€)", + "add_product": "Agregar Producto", + "modify_quantities": "Modifica las cantidades, unidades y precios según sea necesario", + "at_least_one_product": "Por favor, agrega al menos un producto", + "quantities_greater_zero": "Todas las cantidades deben ser mayores a 0", + "products_need_names": "Todos los productos deben tener un nombre", + "priority_urgent": "Urgente", + "priority_high": "Alta", + "priority_normal": "Normal", + "priority_low": "Baja", + "unit_kg": "Kilogramos", + "unit_g": "Gramos", + "unit_l": "Litros", + "unit_ml": "Mililitros", + "unit_units": "Unidades", + "unit_boxes": "Cajas", + "unit_bags": "Bolsas", "status": { "draft": "Borrador", "pending_approval": "Pendiente de Aprobación", @@ -31,6 +75,8 @@ "actions": { "approve": "Aprobar Orden", "modify": "Modificar Orden", - "close": "Cerrar" + "close": "Cerrar", + "save": "Guardar Cambios", + "cancel": "Cancelar" } } diff --git a/frontend/src/locales/eu/purchase_orders.json b/frontend/src/locales/eu/purchase_orders.json index 56206c33..6b70809c 100644 --- a/frontend/src/locales/eu/purchase_orders.json +++ b/frontend/src/locales/eu/purchase_orders.json @@ -3,13 +3,57 @@ "purchase_orders": "Erosketa Aginduak", "created": "Sortua", "supplier": "Hornitzailea", + "supplier_name": "Hornitzailearen Izena", "order_date": "Eskabidearen Data", "expected_delivery": "Espero den Entrega", "items": "Produktuak", "no_items": "Ez dago produkturik eskaera honetan", "notes": "Oharrak", + "order_notes": "Eskaeraren Oharrak", "not_found": "Erosketa agindua ez da aurkitu", "total_amount": "Guztira", + "general_information": "Informazio Orokorra", + "financial_summary": "Finantza Laburpena", + "dates": "Datak", + "po_number": "Agindu Zenbakia", + "status_label": "Egoera", + "quantity": "Kopurua", + "unit_price": "Unitate Prezioa", + "quality_requirements": "Kalitate Baldintzak", + "total": "Guztira", + "priority": "Lehentasuna", + "required_delivery_date": "Beharrezko Entrega Data", + "supplier_info": "Hornitzailearen Informazioa", + "order_details": "Eskaeraren Xehetasunak", + "products": "Produktuak", + "modify_order": "Erosketa Agindua Aldatu", + "modifying_order": "Agindua Aldatzen", + "supplier_cannot_modify": "Hornitzailea ezin da aldatu", + "adjust_priority": "Doitu eskaera honen lehentasuna", + "delivery_deadline": "Entregaren muga data", + "special_instructions": "Hornitzailearentzako jarraibide bereziak...", + "additional_info": "Informazio gehigarria edo jarraibide bereziak", + "product": "Produktua", + "sku": "SKU", + "ordered_quantity": "Eskatutako Kopurua", + "unit": "Unitatea", + "unit_price_euro": "Unitate Prezioa (€)", + "add_product": "Produktua Gehitu", + "modify_quantities": "Aldatu kopuruak, unitateak eta prezioak behar den bezala", + "at_least_one_product": "Mesedez, gehitu gutxienez produktu bat", + "quantities_greater_zero": "Kopuru guztiak 0 baino handiagoak izan behar dira", + "products_need_names": "Produktu guztiek izena izan behar dute", + "priority_urgent": "Premiazkoa", + "priority_high": "Altua", + "priority_normal": "Normala", + "priority_low": "Baxua", + "unit_kg": "Kilogramoak", + "unit_g": "Gramoak", + "unit_l": "Litroak", + "unit_ml": "Mililitroak", + "unit_units": "Unitateak", + "unit_boxes": "Kutxak", + "unit_bags": "Poltsak", "status": { "draft": "Zirriborroa", "pending_approval": "Onarpenaren Zain", @@ -31,6 +75,8 @@ "actions": { "approve": "Agindua Onartu", "modify": "Agindua Aldatu", - "close": "Itxi" + "close": "Itxi", + "save": "Aldaketak Gorde", + "cancel": "Ezeztatu" } } diff --git a/frontend/src/pages/app/DashboardPage.tsx b/frontend/src/pages/app/DashboardPage.tsx index 059cb2f0..5496744c 100644 --- a/frontend/src/pages/app/DashboardPage.tsx +++ b/frontend/src/pages/app/DashboardPage.tsx @@ -37,7 +37,6 @@ import { OrchestrationSummaryCard } from '../../components/dashboard/Orchestrati import { ProductionTimelineCard } from '../../components/dashboard/ProductionTimelineCard'; import { InsightsGrid } from '../../components/dashboard/InsightsGrid'; import { PurchaseOrderDetailsModal } from '../../components/dashboard/PurchaseOrderDetailsModal'; -import { ModifyPurchaseOrderModal } from '../../components/domain/procurement/ModifyPurchaseOrderModal'; import { UnifiedAddWizard } from '../../components/domain/unified-wizard'; import type { ItemType } from '../../components/domain/unified-wizard'; import { useDemoTour, shouldStartTour, clearTourStartPending } from '../../features/demo-onboarding'; @@ -57,10 +56,7 @@ export function NewDashboardPage() { // PO Details Modal state const [selectedPOId, setSelectedPOId] = useState(null); const [isPOModalOpen, setIsPOModalOpen] = useState(false); - - // PO Modify Modal state - const [modifyPOId, setModifyPOId] = useState(null); - const [isModifyPOModalOpen, setIsModifyPOModalOpen] = useState(false); + const [poModalMode, setPOModalMode] = useState<'view' | 'edit'>('view'); // Data fetching const { @@ -128,12 +124,6 @@ export function NewDashboardPage() { setIsPOModalOpen(true); }; - const handleModify = (actionId: string) => { - // Open modal to modify PO - setModifyPOId(actionId); - setIsModifyPOModalOpen(true); - }; - const handleStartBatch = async (batchId: string) => { try { await startBatch.mutateAsync({ tenantId, batchId }); @@ -283,7 +273,6 @@ export function NewDashboardPage() { onApprove={handleApprove} onReject={handleReject} onViewDetails={handleViewDetails} - onModify={handleModify} tenantId={tenantId} /> @@ -366,35 +355,20 @@ export function NewDashboardPage() { onComplete={handleAddWizardComplete} /> - {/* Purchase Order Details Modal */} + {/* Purchase Order Details Modal - Unified View/Edit */} {selectedPOId && ( { setIsPOModalOpen(false); setSelectedPOId(null); - }} - onApprove={handleApprove} - onModify={handleModify} - /> - )} - - {/* Modify Purchase Order Modal */} - {modifyPOId && ( - { - setIsModifyPOModalOpen(false); - setModifyPOId(null); - }} - onSuccess={() => { - setIsModifyPOModalOpen(false); - setModifyPOId(null); + setPOModalMode('view'); handleRefreshAll(); }} + onApprove={handleApprove} /> )}