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 { 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 { SupplierSummary } from '../../../api/types/suppliers'; import type { IngredientResponse } from '../../../api/types/inventory'; import { statusColors } from '../../../styles/colors'; interface CreatePurchaseOrderModalProps { isOpen: boolean; onClose: () => void; requirements: ProcurementRequirementResponse[]; onSuccess?: () => void; } /** * CreatePurchaseOrderModal - Modal for creating purchase orders from procurement requirements * Allows supplier selection and purchase order creation for ingredients * Can also be used for manual purchase order creation when no requirements are provided */ export const CreatePurchaseOrderModal: React.FC = ({ isOpen, onClose, requirements, onSuccess }) => { const [loading, setLoading] = useState(false); const [selectedSupplier, setSelectedSupplier] = useState(''); const [formData, setFormData] = useState>({}); // Get current tenant const { currentTenant } = useTenantStore(); const tenantId = currentTenant?.id || ''; // Fetch suppliers (without status filter to avoid backend enum issue) const { data: suppliersData, isLoading: isLoadingSuppliers, isError: isSuppliersError, error: suppliersError } = useSuppliers( tenantId, { limit: 100 }, { enabled: !!tenantId && isOpen } ); const suppliers = (suppliersData || []).filter(supplier => supplier.status === 'active'); // State for supplier products const [supplierProductIds, setSupplierProductIds] = useState([]); const [isLoadingSupplierProducts, setIsLoadingSupplierProducts] = useState(false); // Fetch ALL ingredients (we'll filter client-side based on supplier products) const { data: allIngredientsData = [], isLoading: isLoadingIngredients } = useIngredients( tenantId, {}, { enabled: !!tenantId && isOpen && !requirements?.length } ); // Fetch supplier products when supplier is selected useEffect(() => { const fetchSupplierProducts = async () => { if (!selectedSupplier || !tenantId) { setSupplierProductIds([]); return; } setIsLoadingSupplierProducts(true); try { const products = await suppliersService.getSupplierProducts(tenantId, selectedSupplier); const productIds = products.map(p => p.inventory_product_id); setSupplierProductIds(productIds); } catch (error) { console.error('Error fetching supplier products:', error); setSupplierProductIds([]); } finally { setIsLoadingSupplierProducts(false); } }; fetchSupplierProducts(); }, [selectedSupplier, tenantId]); // Filter ingredients based on supplier products const ingredientsData = useMemo(() => { if (!selectedSupplier || supplierProductIds.length === 0) { return []; } return allIngredientsData.filter(ing => supplierProductIds.includes(ing.id)); }, [allIngredientsData, supplierProductIds, selectedSupplier]); // Create purchase order mutation const createPurchaseOrderMutation = useCreatePurchaseOrder(); const supplierOptions = useMemo(() => suppliers.map(supplier => ({ value: supplier.id, label: `${supplier.name} (${supplier.supplier_code})` })), [suppliers]); // Create ingredient options from supplier-filtered ingredients const ingredientOptions = useMemo(() => ingredientsData.map(ingredient => ({ value: ingredient.id, label: ingredient.name, data: ingredient // Store full ingredient data for later use })), [ingredientsData]); // Reset selected supplier when modal closes useEffect(() => { if (!isOpen) { setSelectedSupplier(''); setFormData({}); } }, [isOpen]); // Unit options for select field const unitOptions = [ { value: 'kg', label: 'Kilogramos' }, { value: 'g', label: 'Gramos' }, { value: 'l', label: 'Litros' }, { value: 'ml', label: 'Mililitros' }, { value: 'units', label: 'Unidades' }, { value: 'boxes', label: 'Cajas' }, { value: 'bags', label: 'Bolsas' } ]; const handleSave = async (formData: Record) => { setLoading(true); try { let items: PurchaseOrderItem[] = []; if (requirements && requirements.length > 0) { // Create items from requirements list const requiredIngredients = formData.required_ingredients || []; if (requiredIngredients.length === 0) { throw new Error('Por favor, selecciona al menos un ingrediente'); } // Validate quantities const invalidQuantities = requiredIngredients.some((item: any) => item.quantity <= 0); if (invalidQuantities) { throw new Error('Todas las cantidades deben ser mayores a 0'); } // Prepare purchase order items from requirements items = requiredIngredients.map((item: any) => { // Find original requirement to get product_id 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, quality_requirements: originalReq?.quality_specifications ? JSON.stringify(originalReq.quality_specifications) : undefined, notes: originalReq?.special_requirements || undefined }; }); } else { // Create items from manual entries const manualProducts = formData.manual_products || []; if (manualProducts.length === 0) { throw new Error('Por favor, agrega al menos un producto'); } // Validate quantities for manual items const invalidQuantities = manualProducts.some((item: any) => item.quantity <= 0); if (invalidQuantities) { throw new Error('Todas las cantidades deben ser mayores a 0'); } // Validate required fields const invalidProducts = manualProducts.some((item: any) => !item.ingredient_id); if (invalidProducts) { throw new Error('Todos los productos deben tener un ingrediente seleccionado'); } // 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, quality_requirements: undefined, notes: undefined }; }); } // Create purchase order await createPurchaseOrderMutation.mutateAsync({ supplier_id: formData.supplier_id, priority: 'normal', required_delivery_date: formData.delivery_date || undefined, notes: formData.notes || undefined, items }); // Purchase order created successfully // Trigger success callback if (onSuccess) { onSuccess(); } } catch (error) { console.error('Error creating purchase order:', error); throw error; // Let AddModal handle error display } finally { setLoading(false); } }; const statusConfig = { color: statusColors.inProgress.primary, text: 'Nueva Orden', icon: Plus, isCritical: false, isHighlight: true }; // Build sections dynamically based on selectedSupplier const sections = useMemo(() => { const supplierSection = { title: 'Información del Proveedor', icon: Building2, fields: [ { label: 'Proveedor', name: 'supplier_id', type: 'select' as const, required: true, options: supplierOptions, placeholder: 'Seleccionar proveedor...', span: 2, validation: (value: any) => { // Update selectedSupplier when supplier changes if (value && value !== selectedSupplier) { setTimeout(() => setSelectedSupplier(value), 0); } return null; } } ] }; const orderDetailsSection = { title: 'Detalles de la Orden', icon: Calendar, fields: [ { label: 'Fecha de Entrega Requerida', name: 'delivery_date', type: 'date' as const, helpText: 'Fecha límite para la entrega (opcional)' }, { label: 'Notas', name: 'notes', type: 'textarea' as const, placeholder: 'Instrucciones especiales para el proveedor...', span: 2, helpText: 'Información adicional o instrucciones especiales' } ] }; const ingredientsSection = { title: requirements && requirements.length > 0 ? 'Ingredientes Requeridos' : 'Productos a Comprar', icon: Package, fields: [ requirements && requirements.length > 0 ? { label: 'Ingredientes Requeridos', name: 'required_ingredients', type: 'list' as const, span: 2, defaultValue: requirements.map(req => ({ id: req.id, product_name: req.product_name, product_sku: req.product_sku || '', quantity: req.approved_quantity || req.net_requirement || req.required_quantity, unit_of_measure: req.unit_of_measure, unit_price: req.estimated_unit_cost || 0, selected: true })), listConfig: { itemFields: [ { name: 'product_name', label: 'Producto', type: 'text', required: false // Read-only display }, { name: 'product_sku', label: 'SKU', type: 'text', required: false }, { name: 'quantity', label: 'Cantidad Requerida', type: 'number', required: true }, { name: 'unit_of_measure', label: 'Unidad', type: 'text', required: false }, { name: 'unit_price', label: 'Precio Est. (€)', type: 'currency', required: true } ], addButtonLabel: 'Agregar Ingrediente', emptyStateText: 'No hay ingredientes requeridos', showSubtotals: true, subtotalFields: { quantity: 'quantity', price: 'unit_price' } }, helpText: 'Revisa y ajusta las cantidades y precios de los ingredientes requeridos' } : { label: selectedSupplier ? 'Productos a Comprar' : 'Selecciona un proveedor primero', name: 'manual_products', type: 'list' as const, span: 2, defaultValue: [], listConfig: { itemFields: [ { name: 'ingredient_id', label: 'Ingrediente', type: 'select', required: true, options: ingredientOptions, placeholder: isLoadingSupplierProducts || isLoadingIngredients ? 'Cargando ingredientes...' : ingredientOptions.length === 0 ? 'No hay ingredientes disponibles para este proveedor' : 'Seleccionar ingrediente...', disabled: !selectedSupplier || isLoadingIngredients || isLoadingSupplierProducts }, { name: 'quantity', label: 'Cantidad', type: 'number', required: true, defaultValue: 1 }, { name: 'unit_of_measure', label: 'Unidad', type: 'select', required: true, defaultValue: 'kg', options: unitOptions }, { name: 'unit_price', label: 'Precio Unitario (€)', type: 'currency', required: true, defaultValue: 0, placeholder: '0.00' } ], addButtonLabel: 'Agregar Ingrediente', emptyStateText: !selectedSupplier ? 'Selecciona un proveedor para agregar ingredientes' : isLoadingSupplierProducts || isLoadingIngredients ? 'Cargando ingredientes del proveedor...' : ingredientOptions.length === 0 ? 'Este proveedor no tiene ingredientes asignados en su lista de precios' : 'No hay ingredientes agregados', showSubtotals: true, subtotalFields: { quantity: 'quantity', price: 'unit_price' }, disabled: !selectedSupplier }, helpText: !selectedSupplier ? 'Primero selecciona un proveedor en la sección anterior' : 'Selecciona ingredientes disponibles del proveedor seleccionado' } ] }; return [supplierSection, orderDetailsSection, ingredientsSection]; }, [requirements, supplierOptions, ingredientOptions, selectedSupplier, isLoadingIngredients, unitOptions]); return ( <> 0 ? "Generar orden de compra desde requerimientos de procuración" : "Crear orden de compra manual"} statusIndicator={statusConfig} sections={sections} size="xl" loading={loading} onSave={handleSave} /> ); }; export default CreatePurchaseOrderModal;