diff --git a/backend/api/v1/modules/a76/invoices/imports/temporary/validators/common.py b/backend/api/v1/modules/a76/invoices/imports/temporary/validators/common.py index fba8e4f2..55ca2578 100644 --- a/backend/api/v1/modules/a76/invoices/imports/temporary/validators/common.py +++ b/backend/api/v1/modules/a76/invoices/imports/temporary/validators/common.py @@ -5,6 +5,7 @@ from api.v1.modules.a76.general_catalogs.exchange_rate.models import ExchangeRat from api.v1.modules.a76.clients_and_providers.models import ClientProvider from api.v1.modules.a76.customs_brokers.models import CustomsBroker from api.v1.modules.public.reference_data.incoterms.models import Incoterm +from ....models import InvoiceComplianceMx from api.v1.modules.a76.items.models import Item from api.v1.modules.public.reference_data.currency_types.models import CurrencyType from api.v1.modules.public.reference_data.customs_sections.models import CustomsSection @@ -39,7 +40,7 @@ def validate_common( ) if not invoice.compliance_mx.is_regime_change: - if not pedimento.operation_type == 1: + if not pedimento.operation_type == "imp": errors.add_error( field="compliance_mx.pedimento_id", message="El Pedimento seleccionado no corresponde a una Importación.", @@ -107,10 +108,24 @@ def validate_common( ) if pedimento.pedimento_type == "consolidated": - if ( - invoice.invoice_date < pedimento.pedimento_dates.entry_date - or invoice.invoice_date > pedimento.pedimento_dates.end_date - ): + # Convertir invoice_date a date si es datetime para poder comparar + invoice_date = ( + invoice.invoice_date.date() + if hasattr(invoice.invoice_date, "date") + else invoice.invoice_date + ) + entry_date = ( + pedimento.pedimento_dates.entry_date.date() + if hasattr(pedimento.pedimento_dates.entry_date, "date") + else pedimento.pedimento_dates.entry_date + ) + end_date = ( + pedimento.pedimento_dates.end_date.date() + if hasattr(pedimento.pedimento_dates.end_date, "date") + else pedimento.pedimento_dates.end_date + ) + + if invoice_date < entry_date or invoice_date > end_date: errors.add_error( field="invoice_date", message=f"La Fecha de la Factura {invoice.invoice_date} no está dentro del rango de fechas del Pedimento {pedimento.customs_office}-{pedimento.license}-{pedimento.pedimento_number}.", @@ -139,23 +154,23 @@ def validate_common( ) duplicated_remesa = ( - db.query(Pedimentos) + db.query(InvoiceComplianceMx) .filter( - Pedimentos.remesa == invoice.compliance_mx.remesa, - Pedimentos.id != invoice.compliance_mx.pedimento_id, - Pedimentos.tenant_id == tenant_id, - Pedimentos.company_id == company_id, + InvoiceComplianceMx.remesa == invoice.compliance_mx.remesa, + InvoiceComplianceMx.tenant_id == tenant_id, + InvoiceComplianceMx.company_id == company_id, ) .first() ) - if duplicated_remesa: - errors.add_error( - field="compliance_mx.remesa", - message="El valor de Remesa ya está asociado a otro Pedimento.", - solution=["Proporciona un valor único para Remesa"], - code="DUPLICATE_VALUE", - value=invoice.compliance_mx.remesa, - ) + if duplicated_remesa and hasattr(invoice, "id"): + if invoice.id != duplicated_remesa.invoice_id: + errors.add_error( + field="compliance_mx.remesa", + message="El valor de Remesa ya está asociado a otro Pedimento.", + solution=["Proporciona un valor único para Remesa"], + code="DUPLICATE_VALUE", + value=invoice.compliance_mx.remesa, + ) else: if not invoice.compliance_mx.is_pedimento_pending: errors.add_error( @@ -388,7 +403,11 @@ def validate_common( value=invoice.financials.currency, ) - if invoice.financials.currency == "manual": + if invoice.financials.currency == "foreign": + invoice.financials.currency_type = "USD" + elif invoice.financials.currency == "local": + invoice.financials.currency_type = "MXN" + elif invoice.financials.currency == "manual": if not invoice.financials.currency_type: errors.add_error( field="financials.currency_type", diff --git a/frontend/src/lib/api/dashboard/a76/invoices.ts b/frontend/src/lib/api/dashboard/a76/invoices.ts index 089f8ad0..a8ec14de 100644 --- a/frontend/src/lib/api/dashboard/a76/invoices.ts +++ b/frontend/src/lib/api/dashboard/a76/invoices.ts @@ -228,7 +228,7 @@ export interface Invoice { enajenation_goods?: boolean | null; compliance_mx?: InvoiceComplianceMx | null; financials?: InvoiceFinancials | null; - logistics?: InvoiceLogistics[]; + logistics?: InvoiceLogistics; details?: InvoiceSalesDetails[]; collections?: InvoiceCollections[]; } diff --git a/frontend/src/lib/components/dashboard/invoices/columns.ts b/frontend/src/lib/components/dashboard/invoices/columns.ts index a4deee43..e9860959 100644 --- a/frontend/src/lib/components/dashboard/invoices/columns.ts +++ b/frontend/src/lib/components/dashboard/invoices/columns.ts @@ -18,6 +18,43 @@ function formatDate(date?: string | null): string { export function createColumns(onSuccess?: () => void): ColumnDef[] { return [ + { + accessorKey: "operation_type", + header: "Operación", + cell: ({ row }) => { + const operationType = row.original.operation_type; + + const operationSnippet = createRawSnippet<[{ type?: string | null }]>((getType) => { + const { type } = getType(); + const isImport = type === 'imp'; + const colorClass = isImport ? 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200' : 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200'; + const label = isImport ? 'Importación' : type === 'exp' ? 'Exportación' : '-'; + return { + render: () => + ` + ${label} + ` + }; + }); + return renderSnippet(operationSnippet, { type: operationType }); + } + }, + { + accessorKey: "invoice_type", + header: "Tipo Factura", + cell: ({ row }) => { + const invoiceType = row.original.invoice_type; + + const typeSnippet = createRawSnippet<[{ type?: string | null }]>((getType) => { + const { type } = getType(); + return { + render: () => + `
${type || '-'}
` + }; + }); + return renderSnippet(typeSnippet, { type: invoiceType }); + } + }, { accessorKey: "invoice_number", header: "Núm. Factura", @@ -113,7 +150,7 @@ export function createColumns(onSuccess?: () => void): ColumnDef[] { }, { accessorKey: "total_items", - header: "Total Items", + header: "Total Partidas", cell: ({ row }) => { // El total de items viene del conteo de details const totalItems = row.original.details?.length || 0; @@ -165,7 +202,7 @@ export function createColumns(onSuccess?: () => void): ColumnDef[] { header: "Tipo Peso", cell: ({ row }) => { // weight_type está en logistics que es un array, tomamos el primer elemento - const weightType = row.original.logistics?.[0]?.weight_type; + const weightType = row.original.logistics?.weight_type; const weightSnippet = createRawSnippet<[{ type?: string | null }]>((getType) => { const { type } = getType(); diff --git a/frontend/src/lib/components/dashboard/invoices/edit/general-tab-form.svelte b/frontend/src/lib/components/dashboard/invoices/edit/general-tab-form.svelte index 6c42b0ac..c7875be8 100644 --- a/frontend/src/lib/components/dashboard/invoices/edit/general-tab-form.svelte +++ b/frontend/src/lib/components/dashboard/invoices/edit/general-tab-form.svelte @@ -71,11 +71,11 @@ exchange_rate: invoice.financials?.exchange_rate || null, // Added exchange_rate weight_type: 'kgs', iva_factor: invoice.financials?.iva_factor || null, - carrier_id: invoice.logistics?.[0]?.carrier_id || null, - transport_id: invoice.logistics?.[0]?.transport_id || '', - driver_name: invoice.logistics?.[0]?.driver_name || '', - transport_type: invoice.logistics?.[0]?.transport_type || '', - transport_num: invoice.logistics?.[0]?.vehicle_num || '', + carrier_id: invoice.logistics?.carrier_id || null, + transport_id: invoice.logistics?.transport_id || '', + driver_name: invoice.logistics?.driver_name || '', + transport_type: invoice.logistics?.transport_type || '', + transport_num: invoice.logistics?.vehicle_num || '', aduana: invoice.compliance_mx?.aduana || '', document_type: invoice.document_type || '', };