Improve backend

This commit is contained in:
Urtzi Alfaro
2025-11-18 07:17:17 +01:00
parent d36f2ab9af
commit 5c45164c8e
61 changed files with 9846 additions and 495 deletions

View File

@@ -0,0 +1,265 @@
import React, { useState, useEffect, useMemo } from 'react';
import { Edit, Package, Calendar, Building2 } from 'lucide-react';
import { AddModal } from '../../ui/AddModal/AddModal';
import { useUpdatePurchaseOrder, usePurchaseOrder } from '../../../api/hooks/purchase-orders';
import { useTenantStore } from '../../../stores/tenant.store';
import type { PurchaseOrderItem } from '../../../api/types/orders';
import { statusColors } from '../../../styles/colors';
interface ModifyPurchaseOrderModalProps {
isOpen: boolean;
onClose: () => void;
poId: string;
onSuccess?: () => void;
}
/**
* ModifyPurchaseOrderModal - Modal for modifying existing purchase orders
* Allows editing of items, delivery dates, and notes for pending approval POs
*/
export const ModifyPurchaseOrderModal: React.FC<ModifyPurchaseOrderModalProps> = ({
isOpen,
onClose,
poId,
onSuccess
}) => {
const [loading, setLoading] = useState(false);
const [formData, setFormData] = useState<Record<string, any>>({});
// Get current tenant
const { currentTenant } = useTenantStore();
const tenantId = currentTenant?.id || '';
// Fetch the purchase order details
const { data: purchaseOrder, isLoading: isLoadingPO } = usePurchaseOrder(
tenantId,
poId,
{ enabled: !!tenantId && !!poId && isOpen }
);
// Update purchase order mutation
const updatePurchaseOrderMutation = useUpdatePurchaseOrder();
// 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' }
];
// Priority options
const priorityOptions = [
{ value: 'urgent', label: 'Urgente' },
{ value: 'high', label: 'Alta' },
{ value: 'normal', label: 'Normal' },
{ value: 'low', label: 'Baja' }
];
// Reset form when modal closes
useEffect(() => {
if (!isOpen) {
setFormData({});
}
}, [isOpen]);
const handleSave = async (formData: Record<string, any>) => {
setLoading(true);
try {
const items = formData.items || [];
if (items.length === 0) {
throw new Error('Por favor, agrega al menos un producto');
}
// Validate quantities
const invalidQuantities = items.some((item: any) => item.ordered_quantity <= 0);
if (invalidQuantities) {
throw new Error('Todas las cantidades deben ser mayores a 0');
}
// Validate required fields
const invalidProducts = items.some((item: any) => !item.product_name);
if (invalidProducts) {
throw new Error('Todos los productos deben tener un nombre');
}
// 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
});
// Trigger success callback
if (onSuccess) {
onSuccess();
}
} catch (error) {
console.error('Error modifying purchase order:', error);
throw error; // Let AddModal handle error display
} finally {
setLoading(false);
}
};
const statusConfig = {
color: statusColors.pending.primary,
text: 'Modificando Orden',
icon: Edit,
isCritical: false,
isHighlight: true
};
// Build sections dynamically based on purchase order data
const sections = useMemo(() => {
if (!purchaseOrder) return [];
const supplierSection = {
title: 'Información del Proveedor',
icon: Building2,
fields: [
{
label: 'Proveedor',
name: 'supplier_name',
type: 'text' as const,
required: false,
defaultValue: purchaseOrder.supplier_name || '',
span: 2,
disabled: true,
helpText: 'El proveedor no puede ser modificado'
}
]
};
const orderDetailsSection = {
title: 'Detalles de la Orden',
icon: Calendar,
fields: [
{
label: 'Prioridad',
name: 'priority',
type: 'select' as const,
options: priorityOptions,
defaultValue: purchaseOrder.priority || 'normal',
helpText: 'Ajusta la prioridad de esta orden'
},
{
label: 'Fecha de Entrega Requerida',
name: 'required_delivery_date',
type: 'date' as const,
defaultValue: purchaseOrder.required_delivery_date || '',
helpText: 'Fecha límite para la entrega'
},
{
label: 'Notas',
name: 'notes',
type: 'textarea' as const,
placeholder: 'Instrucciones especiales para el proveedor...',
span: 2,
defaultValue: purchaseOrder.notes || '',
helpText: 'Información adicional o instrucciones especiales'
}
]
};
const itemsSection = {
title: 'Productos de la Orden',
icon: Package,
fields: [
{
label: 'Productos',
name: 'items',
type: 'list' as const,
span: 2,
defaultValue: (purchaseOrder.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),
})),
listConfig: {
itemFields: [
{
name: 'product_name',
label: 'Producto',
type: 'text',
required: true,
disabled: true
},
{
name: 'product_code',
label: 'SKU',
type: 'text',
required: false,
disabled: true
},
{
name: 'ordered_quantity',
label: 'Cantidad',
type: 'number',
required: true
},
{
name: 'unit_of_measure',
label: 'Unidad',
type: 'select',
required: true,
options: unitOptions
},
{
name: 'unit_price',
label: 'Precio Unitario (€)',
type: 'currency',
required: true,
placeholder: '0.00'
}
],
addButtonLabel: 'Agregar Producto',
emptyStateText: 'No hay productos en esta orden',
showSubtotals: true,
subtotalFields: { quantity: 'ordered_quantity', price: 'unit_price' },
disabled: false
},
helpText: 'Modifica las cantidades, unidades y precios según sea necesario'
}
]
};
return [supplierSection, orderDetailsSection, itemsSection];
}, [purchaseOrder, priorityOptions, unitOptions]);
return (
<AddModal
isOpen={isOpen}
onClose={onClose}
title="Modificar Orden de Compra"
subtitle={`Orden ${purchaseOrder?.po_number || ''}`}
statusIndicator={statusConfig}
sections={sections}
size="xl"
loading={loading || isLoadingPO}
onSave={handleSave}
/>
);
};
export default ModifyPurchaseOrderModal;