diff --git a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/review_qty_vs_weight.py b/backend/api/v1/modules/a76/invoices/exports/process/sub_process/review_qty_vs_weight.py index 7dad03d8..3aa3b74e 100644 --- a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/review_qty_vs_weight.py +++ b/backend/api/v1/modules/a76/invoices/exports/process/sub_process/review_qty_vs_weight.py @@ -61,7 +61,7 @@ def review_qty_vs_weight( continue qty = line.quantity.quantity or Decimal(0) - net_weight = getattr(line.quantity.quantity, None) or Decimal(0) + net_weight = line.quantity.net_weight or Decimal(0) if net_weight != qty: errors.add_error( diff --git a/backend/api/v1/modules/a76/invoices/imports/process/main_process.py b/backend/api/v1/modules/a76/invoices/imports/process/main_process.py index df03b322..8cc83c7f 100644 --- a/backend/api/v1/modules/a76/invoices/imports/process/main_process.py +++ b/backend/api/v1/modules/a76/invoices/imports/process/main_process.py @@ -198,7 +198,7 @@ def _update_invoice_totals(invoice: InvoiceHeader) -> None: # Marcar la factura como procesada invoice.status = InvoiceStatus.PROCESSED - invoice.party_count = len(invoice.financials.__dict__) # se sobreescribirá con el conteo real + invoice.party_count = len(lines) # TODO: SSisGen:ActSeguridad = 1 → invoice.updated_by = current_user # TODO: SSisGen:CalValBaseTCPed = 1 → diff --git a/backend/api/v1/modules/a76/invoices/services.py b/backend/api/v1/modules/a76/invoices/services.py index 63304a24..a9871ad6 100644 --- a/backend/api/v1/modules/a76/invoices/services.py +++ b/backend/api/v1/modules/a76/invoices/services.py @@ -10,6 +10,7 @@ from .imports.validators.update import validate_update as validate_update_import from .exports.validators.create import validate_create as validate_create_export from .exports.validators.update import validate_update as validate_update_export from .common.common_validators import invoice_exists +from api.v1.modules.a76.items.models import LineItem from . import models, schemas @@ -166,6 +167,25 @@ class InvoiceService: total = query.count() items = query.offset(skip).limit(limit).all() + + # Keep party_count aligned with the real number of line items. + # This avoids stale values stored in invoice_header.party_count. + if items: + invoice_ids = [inv.id for inv in items] + counts = ( + db.query(LineItem.invoice_id, func.count(LineItem.id)) + .filter( + LineItem.invoice_id.in_(invoice_ids), + LineItem.tenant_id == tenant_id, + LineItem.company_id == company_id, + ) + .group_by(LineItem.invoice_id) + .all() + ) + count_map = {invoice_id: int(count) for invoice_id, count in counts} + for inv in items: + inv.party_count = count_map.get(inv.id, 0) + return items, total @staticmethod diff --git a/backend/api/v1/modules/a76/items/exports/validators/create.py b/backend/api/v1/modules/a76/items/exports/validators/create.py index 9c19d6bf..4446bc4c 100644 --- a/backend/api/v1/modules/a76/items/exports/validators/create.py +++ b/backend/api/v1/modules/a76/items/exports/validators/create.py @@ -248,18 +248,18 @@ def validate_create( # Calcular peso neto en kilogramos (estándar interno) if unit_is_kgs: - if invoice_weight_type == "KGS": + if invoice_weight_type.lower() == "kgs": line.quantity.net_weight = quantity else: # invoice en libras line.quantity.net_weight = quantity * Decimal("2.204624") elif unit_is_lbs: - if invoice_weight_type == "KGS": + if invoice_weight_type.lower() == "kgs": line.quantity.net_weight = quantity / Decimal("2.204624") else: # invoice en libras line.quantity.net_weight = quantity else: # Otra unidad de medida - usar peso capturado y convertir si es necesario - if invoice_weight_type == "KGS": + if invoice_weight_type.lower() == "kgs": # El peso capturado está en kilos line.quantity.net_weight = net_weight_input else: @@ -289,7 +289,7 @@ def validate_create( # Si no se proporcionó peso bruto, calcularlo if not gross_weight_input or gross_weight_input == 0: - if invoice_weight_type == "KGS": + if invoice_weight_type.lower() == "kgs": line.quantity.gross_weight = line.quantity.net_weight + ( package_weight_unit * package_quantity ) @@ -299,7 +299,7 @@ def validate_create( ) else: # Convertir peso bruto capturado según tipo de factura - if invoice_weight_type == "KGS": + if invoice_weight_type.lower() == "kgs": line.quantity.gross_weight = gross_weight_input else: # libras line.quantity.gross_weight = gross_weight_input / Decimal("2.204624") diff --git a/backend/api/v1/modules/a76/items/exports/validators/update.py b/backend/api/v1/modules/a76/items/exports/validators/update.py index 4f8e2c9d..6f9ad8a1 100644 --- a/backend/api/v1/modules/a76/items/exports/validators/update.py +++ b/backend/api/v1/modules/a76/items/exports/validators/update.py @@ -95,21 +95,19 @@ def validate_update( # Se proporcionó nuevo peso neto, convertir según tipo net_weight_input = line.quantity.net_weight - if invoice_weight_type == "KGS": + if invoice_weight_type.lower() == "kgs": line.quantity.net_weight = net_weight_input else: # libras, convertir a kilos line.quantity.net_weight = net_weight_input / Decimal("2.204624") else: # Mantener peso existente - line.quantity.net_weight = existing_line.quantity.net_weight - - print(f"After weight conversion: net_weight={line.quantity.net_weight}, gross_weight={line.quantity.gross_weight}, weight_type={invoice_weight_type}") + line.quantity.net_weight = existing_line.quantity.net_weight # Convertir peso bruto si se proporcionó if line.quantity.gross_weight is not None: gross_weight_input = line.quantity.gross_weight - if invoice_weight_type == "KGS": + if invoice_weight_type.lower() == "kgs": line.quantity.gross_weight = gross_weight_input else: # libras, convertir a kilos line.quantity.gross_weight = gross_weight_input / Decimal("2.204624") diff --git a/backend/api/v1/modules/a76/items/imports/validators/create.py b/backend/api/v1/modules/a76/items/imports/validators/create.py index 14780846..7dc00b1c 100644 --- a/backend/api/v1/modules/a76/items/imports/validators/create.py +++ b/backend/api/v1/modules/a76/items/imports/validators/create.py @@ -232,18 +232,18 @@ def validate_create( # Calcular peso neto en kilogramos (estándar interno) if unit_is_kgs: - if invoice_weight_type == "KGS": + if invoice_weight_type.lower() == "kgs": line.quantity.net_weight = quantity else: # invoice en libras line.quantity.net_weight = quantity * Decimal("2.204624") elif unit_is_lbs: - if invoice_weight_type == "KGS": + if invoice_weight_type.lower() == "kgs": line.quantity.net_weight = quantity / Decimal("2.204624") else: # invoice en libras line.quantity.net_weight = quantity else: # Otra unidad de medida - usar peso capturado y convertir si es necesario - if invoice_weight_type == "KGS": + if invoice_weight_type.lower() == "kgs": # El peso capturado está en kilos line.quantity.net_weight = net_weight_input else: @@ -273,7 +273,7 @@ def validate_create( # Si no se proporcionó peso bruto, calcularlo if not gross_weight_input or gross_weight_input == 0: - if invoice_weight_type == "KGS": + if invoice_weight_type.lower() == "kgs": line.quantity.gross_weight = line.quantity.net_weight + ( package_weight_unit * package_quantity ) @@ -283,7 +283,7 @@ def validate_create( ) else: # Convertir peso bruto capturado según tipo de factura - if invoice_weight_type == "KGS": + if invoice_weight_type.lower() == "kgs": line.quantity.gross_weight = gross_weight_input else: # libras line.quantity.gross_weight = gross_weight_input / Decimal("2.204624") diff --git a/backend/api/v1/modules/a76/items/imports/validators/update.py b/backend/api/v1/modules/a76/items/imports/validators/update.py index c1343a0d..c5bd5e7a 100644 --- a/backend/api/v1/modules/a76/items/imports/validators/update.py +++ b/backend/api/v1/modules/a76/items/imports/validators/update.py @@ -94,21 +94,19 @@ def validate_update( # Se proporcionó nuevo peso neto, convertir según tipo net_weight_input = line.quantity.net_weight - if invoice_weight_type == "KGS": + if invoice_weight_type.lower() == "kgs": line.quantity.net_weight = net_weight_input else: # libras, convertir a kilos line.quantity.net_weight = net_weight_input / Decimal("2.204624") else: # Mantener peso existente - line.quantity.net_weight = existing_line.quantity.net_weight - - print(f"After weight conversion: net_weight={line.quantity.net_weight}, gross_weight={line.quantity.gross_weight}, weight_type={invoice_weight_type}") + line.quantity.net_weight = existing_line.quantity.net_weight # Convertir peso bruto si se proporcionó if line.quantity.gross_weight is not None: gross_weight_input = line.quantity.gross_weight - if invoice_weight_type == "KGS": + if invoice_weight_type.lower() == "kgs": line.quantity.gross_weight = gross_weight_input else: # libras, convertir a kilos line.quantity.gross_weight = gross_weight_input / Decimal("2.204624") diff --git a/frontend/src/lib/api/dashboard/a76/invoices.ts b/frontend/src/lib/api/dashboard/a76/invoices.ts index 371a8159..d3b7d812 100644 --- a/frontend/src/lib/api/dashboard/a76/invoices.ts +++ b/frontend/src/lib/api/dashboard/a76/invoices.ts @@ -232,13 +232,14 @@ export interface Invoice { download_substance?: boolean | null; download_class?: boolean | null; download_def?: boolean | null; + total_items?: number | null; payment_terms?: string | null; handling_fees?: number | null; option_iv18?: string | null; enajenation_goods?: boolean | null; compliance_mx?: InvoiceComplianceMx | null; financials?: InvoiceFinancials | null; - logistics?: InvoiceLogistics[]; + logistics?: InvoiceLogistics | null; details?: InvoiceSalesDetails[]; collections?: InvoiceCollections[]; // Client-side only properties diff --git a/frontend/src/lib/components/dashboard/invoices/columns.ts b/frontend/src/lib/components/dashboard/invoices/columns.ts index 9c4ffd83..7821e55f 100644 --- a/frontend/src/lib/components/dashboard/invoices/columns.ts +++ b/frontend/src/lib/components/dashboard/invoices/columns.ts @@ -6,7 +6,19 @@ import { getInvoiceTypeColor } from "$lib/utils"; function formatDate(date?: string | null): string { if (!date) return '-'; - return new Date(date).toLocaleDateString('es-MX', { + // Avoid timezone shifts (e.g. showing one day earlier) by parsing as local date. + const raw = String(date); + const ymd = raw.includes('T') ? raw.split('T')[0] : raw; + const parts = ymd.split('-').map(Number); + if (parts.length === 3 && parts.every((n) => Number.isFinite(n))) { + const [year, month, day] = parts; + return new Date(year, month - 1, day).toLocaleDateString('es-MX', { + year: 'numeric', + month: '2-digit', + day: '2-digit' + }); + } + return new Date(raw).toLocaleDateString('es-MX', { year: 'numeric', month: '2-digit', day: '2-digit' @@ -243,7 +255,10 @@ export function createColumns( accessorKey: "total_items", header: "Total Partidas", cell: ({ row }) => { - const totalItems = row.original.details?.length || 0; + const totalItems = (row.original as Invoice & { total_items?: number | null }).total_items + ?? row.original.party_count + ?? row.original.details?.length + ?? 0; const itemsSnippet = createRawSnippet<[{ total: number }]>((getTotal) => { const { total } = getTotal(); @@ -291,7 +306,7 @@ export function createColumns( accessorKey: "logistics.weight_type", header: "Tipo Peso", cell: ({ row }) => { - 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(); @@ -303,28 +318,6 @@ export function createColumns( return renderSnippet(weightSnippet, { type: weightType }); } }, - { - accessorKey: "status", - header: "Actualizado", - cell: ({ row }) => { - // status can be 'processed' | 'pending' | 'reversed' (string) or legacy boolean - const s = row.original.status; - const isprocessed = s === "processed" || s === true; - - const processedSnippet = createRawSnippet<[{ isprocessed?: boolean | null }]>((getprocessed) => { - const { isprocessed } = getprocessed(); - const colorClass = isprocessed ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800'; - const label = isprocessed ? 'Sí' : 'No'; - return { - render: () => - ` - ${label} - ` - }; - }); - return renderSnippet(processedSnippet, { isprocessed }); - } - }, { accessorKey: "compliance_mx.is_mixed", header: "Mixto", diff --git a/frontend/src/lib/components/dashboard/invoices/data-table.svelte b/frontend/src/lib/components/dashboard/invoices/data-table.svelte index cb2c4dbc..29b7fb10 100644 --- a/frontend/src/lib/components/dashboard/invoices/data-table.svelte +++ b/frontend/src/lib/components/dashboard/invoices/data-table.svelte @@ -83,9 +83,21 @@