diff --git a/backend/alembic/versions/7937209f9718_seed_initial_data.py b/backend/alembic/versions/7937209f9718_seed_initial_data.py index 5bffa511..1a4f86ed 100644 --- a/backend/alembic/versions/7937209f9718_seed_initial_data.py +++ b/backend/alembic/versions/7937209f9718_seed_initial_data.py @@ -528,6 +528,7 @@ def upgrade() -> None: ) if values_historical_fractions: + op.execute("ALTER TABLE a76.historical_tariff_fractions DISABLE TRIGGER ALL;") op.execute( f""" INSERT INTO a76.historical_tariff_fractions @@ -539,6 +540,7 @@ def upgrade() -> None: ON CONFLICT DO NOTHING; """ ) + op.execute("ALTER TABLE a76.historical_tariff_fractions ENABLE TRIGGER ALL;") def downgrade() -> None: diff --git a/backend/api/v1/modules/a76/classes/dto.py b/backend/api/v1/modules/a76/classes/dto.py index 00c141b4..68054f2a 100644 --- a/backend/api/v1/modules/a76/classes/dto.py +++ b/backend/api/v1/modules/a76/classes/dto.py @@ -223,6 +223,7 @@ class ClassWithFADataResponse(BaseModel): fa_class_id: Optional[int] = None depreciation_rate: Optional[Decimal] = None fda_code: Optional[str] = None + eccn_code: Optional[str] = None class_enabled: Optional[bool] = None model_config = ConfigDict(from_attributes=True) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/classes/service.py b/backend/api/v1/modules/a76/classes/service.py index 1e3ba949..4decee1c 100644 --- a/backend/api/v1/modules/a76/classes/service.py +++ b/backend/api/v1/modules/a76/classes/service.py @@ -152,6 +152,7 @@ class ClassService: "fa_class_id": fa_class.id if fa_class else None, "depreciation_rate": fa_class.depreciation_rate if fa_class else None, "fda_code": fa_class.fda_code if fa_class else None, + "eccn_code": fa_class.eccn_code if fa_class else None, "class_enabled": fa_class.class_enabled if fa_class else None, } combined.append(class_dict) diff --git a/backend/api/v1/modules/a76/invoices/catalog_service.py b/backend/api/v1/modules/a76/invoices/catalog_service.py index 9ad64b0b..2dee90a7 100644 --- a/backend/api/v1/modules/a76/invoices/catalog_service.py +++ b/backend/api/v1/modules/a76/invoices/catalog_service.py @@ -134,12 +134,14 @@ class InvoiceCatalogService: # Drivers try: - drivers, _ = DriverService.get_all(db, tenant_id, company_id, limit=1000) + drivers = DriverService.list_drivers(db, str(company_id), str(tenant_id)) response.drivers = [ DriverResponseDTO.model_validate(d) for d in drivers ] except Exception as e: print(f"Error fetching drivers: {e}") + # Initialize drivers as empty list if an error occurs + drivers = [] # Trailers try: diff --git a/backend/api/v1/modules/a76/items/imports/temporary/validators/create.py b/backend/api/v1/modules/a76/items/imports/temporary/validators/create.py index 70dc5245..677df0ed 100644 --- a/backend/api/v1/modules/a76/items/imports/temporary/validators/create.py +++ b/backend/api/v1/modules/a76/items/imports/temporary/validators/create.py @@ -188,6 +188,23 @@ def validate_create( line.financial.unit_cost_mxn = unit_cost_capture # Si es otro tipo de moneda, dejamos el costo como está + # Calcular valores totales basados en cantidad y costo unitario + quantity = line.quantity.quantity or Decimal("0") + + # Valor Comercial + if line.financial.unit_cost_usd is not None: + line.financial.value_usd = line.financial.unit_cost_usd * quantity + if line.financial.unit_cost_mxn is not None: + line.financial.value_mxn = line.financial.unit_cost_mxn * quantity + + # Valor Aduanas (asumiendo que es igual al Valor Comercial por defecto) + line.financial.customs_value_usd = line.financial.value_usd + line.financial.customs_value_mxn = line.financial.value_mxn + + # Valor MP Temp (Materia Prima Temporal) + line.financial.value_temp_material_usd = line.financial.value_usd + line.financial.value_temp_material_mxn = line.financial.value_mxn + # ========================================== # VALIDAR Y CONVERTIR PESOS NETOS # ========================================== diff --git a/backend/api/v1/modules/a76/items/imports/temporary/validators/update.py b/backend/api/v1/modules/a76/items/imports/temporary/validators/update.py index 1f5b917e..23717f2d 100644 --- a/backend/api/v1/modules/a76/items/imports/temporary/validators/update.py +++ b/backend/api/v1/modules/a76/items/imports/temporary/validators/update.py @@ -64,6 +64,30 @@ def validate_update( # Costo unitario if line.financial.unit_cost_capture is None: line.financial.unit_cost_capture = existing_line.financial.unit_cost_capture + + # Recalcular valores monetarios si el costo o la cantidad cambian + currency_type = invoice.financials.currency_type + unit_cost_capture = line.financial.unit_cost_capture or Decimal("0") + + if currency_type in ["USD", "ME"]: + line.financial.unit_cost_usd = unit_cost_capture + line.financial.unit_cost_mxn = unit_cost_capture * exchange_rate + elif currency_type in ["MXN", "MN"]: + line.financial.unit_cost_usd = (unit_cost_capture / exchange_rate) if exchange_rate else Decimal("0") + line.financial.unit_cost_mxn = unit_cost_capture + + quantity = line.quantity.quantity if line.quantity.quantity is not None else existing_line.quantity.quantity + + if line.financial.unit_cost_usd is not None: + line.financial.value_usd = line.financial.unit_cost_usd * quantity + if line.financial.unit_cost_mxn is not None: + line.financial.value_mxn = line.financial.unit_cost_mxn * quantity + + line.financial.customs_value_usd = line.financial.value_usd + line.financial.customs_value_mxn = line.financial.value_mxn + + line.financial.value_temp_material_usd = line.financial.value_usd + line.financial.value_temp_material_mxn = line.financial.value_mxn # Convertir peso neto si se proporcionó invoice_weight_type = invoice.logistics.weight_type diff --git a/backend/api/v1/modules/a76/items/schemas.py b/backend/api/v1/modules/a76/items/schemas.py index cad3a134..b54c914b 100644 --- a/backend/api/v1/modules/a76/items/schemas.py +++ b/backend/api/v1/modules/a76/items/schemas.py @@ -4,7 +4,7 @@ Complete nested one-to-one structure: LineItem -> LineFinancial -> LineQuantity -> LineCustoms -> LineDescription -> LineReference """ -from typing import Any, Optional +from typing import Any, Optional, Union from datetime import datetime from decimal import Decimal from pydantic import BaseModel, Field, ConfigDict, model_validator @@ -58,13 +58,13 @@ class LineItemBase(BaseModel): line_number: int = Field(..., description="Line number") # Part identification - part_number_id: Optional[int] = Field( + part_number_id: Union[int, str, None] = Field( None, description="Part number", alias="part_number", serialization_alias="part_number_id", ) - component_part_number_id: Optional[int] = Field( + component_part_number_id: Union[int, str, None] = Field( None, description="Component part number", alias="component_part_number", diff --git a/backend/api/v1/modules/a76/items/service.py b/backend/api/v1/modules/a76/items/service.py index bf8321af..f75fa1d6 100644 --- a/backend/api/v1/modules/a76/items/service.py +++ b/backend/api/v1/modules/a76/items/service.py @@ -36,6 +36,7 @@ from .line_references.models import LineReference from api.v1.modules.a24.fa.fa_item_lines.models import FaLineItem from .models import LineItem from api.v1.modules.a76.invoices.models import InvoiceHeader +from api.v1.modules.a76.parts.models import Part logger = logging.getLogger(__name__) @@ -45,6 +46,33 @@ class ItemService: Service for managing Items and related entities with tenant/company isolation """ + @staticmethod + def _resolve_part_number( + db: Session, + part_number: Optional[str], + tenant_id: int, + company_id: int, + ) -> Optional[int]: + """Try to resolve a part number string to its database ID.""" + if not part_number: + return None + + # If it's already an integer (or a string representing an integer), it might be the ID + try: + return int(part_number) + except (ValueError, TypeError): + # It's a string part number (e.g., "MAQ-001"), look it up + part = ( + db.query(Part) + .filter( + Part.part_number == part_number, + Part.tenant_id == tenant_id, + Part.company_id == company_id, + ) + .first() + ) + return part.id if part else None + @staticmethod def _get_next_line_number(db: Session, invoice_id: int) -> int: """Calculate the next line_number for a given invoice based on database.""" @@ -282,6 +310,24 @@ class ItemService: # Calculate the next line number for this single item line_number = ItemService._get_next_line_number(db, item_data.invoice_id) + # Resolve part ID if a string is provided in part_number (alias for part_number_id) + if item_data.part_number_id and not isinstance(item_data.part_number_id, int): + resolved_id = ItemService._resolve_part_number( + db, str(item_data.part_number_id), tenant_id, company_id + ) + if resolved_id: + item_data.part_number_id = resolved_id + + # Resolve component part ID + if item_data.component_part_number_id and not isinstance( + item_data.component_part_number_id, int + ): + resolved_id = ItemService._resolve_part_number( + db, str(item_data.component_part_number_id), tenant_id, company_id + ) + if resolved_id: + item_data.component_part_number_id = resolved_id + # Validar el item validate_create( db, @@ -378,6 +424,24 @@ class ItemService: ): errors.raise_if_errors("Error al actualizar el item") + # Resolve part ID if a string is provided in part_number (alias for part_number_id) + if hasattr(item_data, 'part_number_id') and item_data.part_number_id and not isinstance(item_data.part_number_id, int): + resolved_id = ItemService._resolve_part_number( + db, str(item_data.part_number_id), tenant_id, company_id + ) + if resolved_id: + item_data.part_number_id = resolved_id + + # Resolve component part ID + if hasattr(item_data, 'component_part_number_id') and item_data.component_part_number_id and not isinstance( + item_data.component_part_number_id, int + ): + resolved_id = ItemService._resolve_part_number( + db, str(item_data.component_part_number_id), tenant_id, company_id + ) + if resolved_id: + item_data.component_part_number_id = resolved_id + # Validar el item que se va a actualizar validate_update( db, diff --git a/backend/api/v1/modules/a76/reports/movements/invoices/services/database_helpers.py b/backend/api/v1/modules/a76/reports/movements/invoices/services/database_helpers.py index 024d23c3..e6ab3db8 100644 --- a/backend/api/v1/modules/a76/reports/movements/invoices/services/database_helpers.py +++ b/backend/api/v1/modules/a76/reports/movements/invoices/services/database_helpers.py @@ -112,9 +112,10 @@ class DatabaseHelper: try: sql = text(""" - SELECT name, rfc - FROM a76.clients_and_providers - WHERE id = :client_code AND client_or_provider = :client_type + SELECT cp.name, cp.rfc, cpp.tax_id + FROM a76.clients_and_providers cp + LEFT JOIN a76.clients_and_providers_programs cpp ON cpp.client_id = cp.id + WHERE cp.id = :client_code AND cp.client_or_provider = :client_type """) result = db.execute(sql, {"client_code": client_code, "client_type": client_type}).fetchone() @@ -122,7 +123,7 @@ class DatabaseHelper: return { "name": result[0], "rfc": result[1], - "tax_id": None # Column does not exist in this table + "tax_id": result[2] } else: logger.debug(f"Client {client_code} not found as {client_type}") @@ -233,8 +234,7 @@ class DatabaseHelper: SELECT serial_numbers, model, brand FROM a76.item_line_series ils INNER JOIN a76.item_lines il ON ils.line_item_id = il.id - INNER JOIN a76.items i ON il.item_id = i.id - WHERE i.invoice_id = :invoice_id + WHERE il.invoice_id = :invoice_id AND il.line_number = :linea ORDER BY ils.id LIMIT 1 @@ -290,8 +290,7 @@ class DatabaseHelper: SELECT serial_numbers, model, expo_brad FROM a76.item_line_series ils INNER JOIN a76.item_lines il ON ils.line_item_id = il.id - INNER JOIN a76.items i ON il.item_id = i.id - WHERE i.invoice_id = :invoice_id + WHERE il.invoice_id = :invoice_id AND il.line_number = :linea ORDER BY ils.id LIMIT 1 diff --git a/backend/api/v1/modules/a76/reports/movements/invoices/services/definitive.py b/backend/api/v1/modules/a76/reports/movements/invoices/services/definitive.py index 927eaf09..dd710d24 100644 --- a/backend/api/v1/modules/a76/reports/movements/invoices/services/definitive.py +++ b/backend/api/v1/modules/a76/reports/movements/invoices/services/definitive.py @@ -89,6 +89,11 @@ class DefinitiveImportService: # Totals come directly from GROUP BY query (no N+1 problem) total_me = to_float(row[28]) # total_me from SUM aggregation total_mn = to_float(row[29]) # total_mn from SUM aggregation + sum_value_usd = to_float(row[30]) + sum_value_mxn = to_float(row[31]) + + total_me = sum_value_usd if sum_value_usd > 0 else total_me + total_mn = sum_value_mxn if sum_value_mxn > 0 else total_mn # Calculate exchange rate and value valor_comercial, tipo_cambio = ExchangeRateCalculator.calculate_for_aggregated( @@ -215,58 +220,58 @@ class DefinitiveImportService: valor_comercial, tipo_cambio = ExchangeRateCalculator.calculate_exchange_rate_and_value( db=db, db_name=filters.database_name, - es_subpartida=row[39], # EsSubPartida - valor_me=row[26], - valor_mn_direct=row[24], - fecha_pago=row[12], - fecha_inicio=row[10], - clave_ped=row[58], - tipo_cambio_partida=row[50], + es_subpartida=row[39], # C40 - EsSubPartida + valor_me=row[26], # C27 - ValorImpoME + valor_mn_direct=row[24], # C25 - ValorImpoMN + fecha_pago=row[12], # C13 - Fecha_Pago + fecha_inicio=row[10], # C1 entry_date + clave_ped=row[57] if len(row) > 57 else '', # C58 - TIPOPEDIMENTOTRANSPORTEE + tipo_cambio_partida=row[49], # C50 - TipoCambio currency_type=filters.currency_type.value, exchange_rate_type=filters.exchange_rate_type.value, met_trans=met_trans ) # Set peso values based on subpartida flag - if row[39] == 'P': - peso_neto = float(row[28]) if row[28] else 0.0 - peso_bruto = float(row[29]) if row[29] else 0.0 + if row[39] == 'P': # C40 - EsSubPartida + peso_neto = float(row[28]) if row[28] else 0.0 # C29 + peso_bruto = float(row[29]) if row[29] else 0.0 # C30 else: peso_neto = 0.0 peso_bruto = 0.0 series_info = DatabaseHelper.get_series_info( - db, filters.database_name, row[34], row[44], filters.is_shelter + db, filters.database_name, row[38], row[43], filters.is_shelter # C39, C44 ) simbolo_ex = None - if row[19]: + if row[48]: # C49 - Part Number simbolo_ex = DatabaseHelper.get_part_export_symbol( - db, filters.database_name, row[19], filters.is_shelter + db, filters.database_name, row[48], filters.is_shelter ) pedimento_r1 = DatabaseHelper.get_rectification_pedimento( - db, row[1], row[41], filters.is_shelter + db, row[1], row[40], filters.is_shelter # C2, C41 ) num_gaf_uni = DatabaseHelper.get_driver_badge( - db, filters.database_name, row[0] + db, filters.database_name, row[0] # C1 ) movement = MovementItemDetailed( - Linea=row[44], - Factura=row[0], - Pedimento=row[1], - FechaFactura=row[2], - Estatus=row[3], - ClavePed=row[4], + Linea=row[43], # C44 + Factura=row[0], # C1 + Pedimento=row[1], # C2 + FechaFactura=parse_yyyymmdd_date(row[2]), # C3 + Estatus=row[3], # C4 + ClavePed=row[4], # C5 TipoMovTemDef='IMPDF', EsCambioRegimen='N', - Regimen=row[9], - Fecha_Inicio=row[10], - Fecha_Fin=row[11], - Fecha_Pago=row[12], - Remesa=row[13], + Regimen=row[9], # C10 + Fecha_Inicio=parse_yyyymmdd_date(row[10]), # C11 + Fecha_Fin=parse_yyyymmdd_date(row[11]), # C12 + Fecha_Pago=parse_yyyymmdd_date(row[12]), # C13 + Remesa=str(row[13]) if row[13] is not None else None, # C14 Proveedor=row[7], # C8 - Provider name (from JOIN) RFCProveedor=proveedor_info.get('rfc'), ProveedorTaxID=proveedor_info.get('tax_id'), @@ -275,42 +280,42 @@ class DefinitiveImportService: VendidoATaxID=vendido_info.get('tax_id'), AgenteAduanal=agente_info.get('name'), Patente=agente_info.get('license'), - NumParte=row[19], - DescripcionE=StringHelper.clean_text(row[20]), - DescripcionI=StringHelper.clean_text(row[21]), - CantidadIE=float(row[22]) if row[22] else 0.0, - UniMed=row[23], + NumParte=row[48], # C49 + DescripcionE=StringHelper.clean_text(row[20]), # C21 + DescripcionI=StringHelper.clean_text(row[21]), # C22 + CantidadIE=float(row[22]) if row[22] else 0.0, # C23 + UniMed=row[23], # C24 ValorComercialMN=valor_comercial, TipoCambio=tipo_cambio, PesoNeto=peso_neto, PesoBruto=peso_bruto, - OrdenCompraVenta=row[30], - FraccionArancelaria=row[31], - Preferencia=row[32], - Sector=None, # row[34] is invoice ID, Sector not in query - PaisOrigen=row[37], + OrdenCompraVenta=row[30], # C31 + FraccionArancelaria=row[31], # C32 + Preferencia=row[32], # C33 + Sector=row[34], # C35 + PaisOrigen=row[36], # C37 Aduana=aduana_nombre, - Advalorem=row[39], + Advalorem='P' if row[39] == 'P' else 'S', # C40 TipoExpo='', PedimentoR1=pedimento_r1, - EDocument=row[42], - NumOperacionVU=row[43], + EDocument=row[41], # C42 + NumOperacionVU=row[42], # C43 Series=series_info, - Marca=StringHelper.clean_text(row[45]), - Modelo=StringHelper.clean_text(row[46]), - FraccionAmericana=row[47], - ECCN=row[48], + Marca=StringHelper.clean_text(row[44]), # C45 + Modelo=StringHelper.clean_text(row[45]), # C46 + FraccionAmericana=row[46], # C47 + ECCN=row[47], # C48 SimboloEx=simbolo_ex, - FechaEmision=row[51], + FechaEmision=parse_yyyymmdd_date(row[50]) if row[50] else None, # C51 BaseDeDatos=filters.database_name, NumGafUni=num_gaf_uni, - UsuarioCap=row[52], - UsuarioAcr=row[53], - Transportista=row[54], - NumCaja=row[55], - Pedimento18=row[56], - AduanaCru=row[38], - Lote=row[57] + UsuarioCap=row[51], # C52 + UsuarioAcr=row[52], # C53 + Transportista=row[53], # C54 + NumCaja=row[54], # C55 + Pedimento18=row[55] if len(row) > 55 else '', # C56 + AduanaCru=row[37], # C38 + Lote=row[56] if len(row) > 56 else '' # C57 ) movements.append(movement) @@ -337,7 +342,7 @@ class DefinitiveImportService: if filters.range_type.value == "FF": where_conditions.append(f"ih.invoice_date >= TO_DATE('{filters.start_date}', 'YYYYMMDD') AND ih.invoice_date <= TO_DATE('{filters.end_date}', 'YYYYMMDD')") else: - where_conditions.append(f"log.payment_date >= TO_DATE('{filters.start_date}', 'YYYYMMDD') AND log.payment_date <= TO_DATE('{filters.end_date}', 'YYYYMMDD')") + where_conditions.append(f"pd.payment_date >= TO_DATE('{filters.start_date}', 'YYYYMMDD') AND pd.payment_date <= TO_DATE('{filters.end_date}', 'YYYYMMDD')") # Note: Status filter applied at Python level after CASE WHEN in SELECT # because is_updated doesn't directly represent AC/NA status @@ -367,8 +372,7 @@ class DefinitiveImportService: COALESCE(SUM(lf.value_mxn), 0) FROM a76.item_line_financials lf INNER JOIN a76.item_lines il ON il.id = lf.item_line_id - INNER JOIN a76.items itm ON itm.id = il.item_id - WHERE itm.invoice_id = :consecutivo + WHERE il.invoice_id = :consecutivo AND COALESCE(il.is_subpartida, false) = false """) diff --git a/backend/api/v1/modules/a76/reports/movements/invoices/services/exchange_rate.py b/backend/api/v1/modules/a76/reports/movements/invoices/services/exchange_rate.py index 656c8869..5c9f2010 100644 --- a/backend/api/v1/modules/a76/reports/movements/invoices/services/exchange_rate.py +++ b/backend/api/v1/modules/a76/reports/movements/invoices/services/exchange_rate.py @@ -65,13 +65,14 @@ class ExchangeRateCalculator: fecha_pago=fecha_pago, fecha_inicio=fecha_inicio, tipo_pedimento=clave_ped, - use_transport_method=True, # Always use for detailed calculations + use_transport_method=True, met_trans=met_trans ) tc_value = DatabaseHelper.get_exchange_rate(db, db_name, fecha_tc) if tc_value: tipo_cambio_final = tc_value + # For ME, the value is always in foreign currency (USD) return (valor_comercial, tipo_cambio_final) # Handle local currency (MN) case @@ -86,14 +87,14 @@ class ExchangeRateCalculator: tc_value = DatabaseHelper.get_exchange_rate(db, db_name, fecha_tc) if tc_value and valor_me is not None: - # Calculate MN value from ME * exchange rate + # Calculate MN value from ME * payment date exchange rate return (valor_me * tc_value, tc_value) else: - # Fall back to direct MN value and partida exchange rate if tc_value is None: logger.warning(f"Exchange rate not found for date {fecha_tc}, using partida values") return (valor_mn_direct or 0.0, tipo_cambio_partida) else: + # exchange_rate_type == "FT" (Invoice Date) # Use direct MN value and partida exchange rate return (valor_mn_direct or 0.0, tipo_cambio_partida) @@ -151,22 +152,42 @@ class ExchangeRateCalculator: use_transport_method=use_transport_method, met_trans=met_trans ) - # For Shelter + FP: validate exchange rate exists (Clarion logic) tc_value = DatabaseHelper.get_exchange_rate( db, db_name, fecha_tc, is_shelter=is_shelter, - raise_on_missing=is_shelter # Raise error if shelter and not found + raise_on_missing=is_shelter ) if tc_value: - return (valor_me * tc_value, tc_value) + # For ME, the value is always in foreign currency (USD). Just return the new exchange rate. + return (valor_comercial, tc_value) else: logger.warning(f"Exchange rate not found for date {fecha_tc}, using DB values") - return (valor_mn, tipo_cambio_db) - else: - # FT or no payment date: use DB values - return (valor_comercial, tipo_cambio) - + + return (valor_comercial, tipo_cambio) + # Local currency case else: - return (valor_mn, tipo_cambio_db) + if exchange_rate_type == "FP" and fecha_pago: + fecha_tc = DateHelper.get_fecha_tipo_cambio( + fecha_pago=fecha_pago, + fecha_inicio=fecha_inicio, + tipo_pedimento=tipo_pedimento, + use_transport_method=use_transport_method, + met_trans=met_trans + ) + tc_value = DatabaseHelper.get_exchange_rate( + db, db_name, fecha_tc, + is_shelter=is_shelter, + raise_on_missing=is_shelter + ) + + if tc_value and valor_me is not None: + # Calculate MN value from ME * payment date exchange rate + return (valor_me * tc_value, tc_value) + else: + if tc_value is None: + logger.warning(f"Exchange rate not found for date {fecha_tc}, using DB values") + return (valor_mn, tipo_cambio_db) + else: + return (valor_mn, tipo_cambio_db) diff --git a/backend/api/v1/modules/a76/reports/movements/invoices/services/export.py b/backend/api/v1/modules/a76/reports/movements/invoices/services/export.py index bec76370..17ceb6f9 100644 --- a/backend/api/v1/modules/a76/reports/movements/invoices/services/export.py +++ b/backend/api/v1/modules/a76/reports/movements/invoices/services/export.py @@ -73,8 +73,18 @@ class ExportService: consecutivo = row[16] # C35 - Consecutivo # Totals come directly from GROUP BY query (no N+1 problem) - total_me = row[24] # total_me from SUM aggregation - total_mn = row[25] # total_mn from SUM aggregation + def to_float(val): + if val is None or val == '': return 0.0 + try: return float(val) + except (ValueError, TypeError): return 0.0 + + total_me = to_float(row[24]) # total_me from SUM aggregation + total_mn = to_float(row[25]) # total_mn from SUM aggregation + sum_value_usd = to_float(row[27]) + sum_value_mxn = to_float(row[28]) + + total_me = sum_value_usd if sum_value_usd > 0 else total_me + total_mn = sum_value_mxn if sum_value_mxn > 0 else total_mn # Calculate exchange rate and value valor_comercial, tipo_cambio = ExchangeRateCalculator.calculate_for_aggregated( @@ -243,19 +253,19 @@ class ExportService: # Build detailed movement item movement = MovementItemDetailed( - Linea=row[41], # C42 - LineaExpo - Factura=row[0], # C1 - FacturaExpo - Pedimento=row[1], # C2 - PedimentoExpo - FechaFactura=row[2], # C3 - FechaFactura - Estatus=row[5], # C6 - Estatus - ClavePed=row[6], # C7 - ClavePed - TipoMovTemDef=row[33], # C34 - TipoFactura + Linea=row[39], # C42 - LineaExpo + Factura=row[0], # C1 - FacturaExpo + Pedimento=row[1], # C2 - PedimentoExpo + FechaFactura=row[2], # C3 - FechaFactura + Estatus=row[5], # C6 - Estatus + ClavePed=row[4], # C5 - ClavePed + TipoMovTemDef=row[31], # C34 - TipoFactura EsCambioRegimen='N', - Regimen=row[7], # C8 - Regimen - Fecha_Inicio=row[8], # C9 - Fecha_Inicio - Fecha_Fin=row[9], # C10 - Fecha_Fin - Fecha_Pago=row[10], # C11 - Fecha_Pago - Remesa=row[11], # C12 - Remesa + Regimen=row[5], # C6 - Regime (Shared index with Estatus in this query) + Fecha_Inicio=parse_yyyymmdd_date(row[6]), # C7 - Fecha_Inicio + Fecha_Fin=parse_yyyymmdd_date(row[7]), # C8 - Fecha_Fin + Fecha_Pago=parse_yyyymmdd_date(row[8]), # C9 - Fecha_Pago + Remesa=row[9], # C12 - Remesa TipoCambio=tipo_cambio_final, Proveedor=proveedor_info.get("name"), RFCProveedor=proveedor_info.get("rfc"), @@ -265,41 +275,42 @@ class ExportService: VendidoATaxID=vendido_info.get("tax_id"), AgenteAduanal=agente_info.get("name"), Patente=agente_info.get("license"), - NumParte=row[17], # C18 - Clase + NumParte=row[17], # C18 - NumParte DescripcionE=StringHelper.remove_commas(row[18]), # C19 - DescripcionE DescripcionI=StringHelper.remove_commas(row[19]), # C20 - DescripcionI - CantidadIE=row[20], # C21 - CantExpo - UniMed=row[21], # C22 - UnidadMedida + CantidadIE=row[20], # C21 - CantExpo + UniMed=row[21], # C22 - UnidadMedida ValorComercialMN=valor_mn, PesoNeto=peso_neto_final, PesoBruto=peso_bruto_final, - OrdenCompraVenta=row[26], # C27 - OrdenCompra - FraccionArancelaria=row[27], # C28 - FraccionExpo - Preferencia=row[28], # C29 - TipoFraccion - Sector=row[30], # C31 - Sector - PaisOrigen=row[31], # C32 - PaisOrigen + OrdenCompraVenta=row[26], # C27 - OrdenCompra + FraccionArancelaria=row[27], # C28 - FraccionExpo + Preferencia=row[28], # C29 - TipoFraccion + Sector=row[30], # C31 - Sector + PaisOrigen=row[31], # C32 - PaisOrigen Aduana=customs_name, - Advalorem=row[37], # C38 - EsSubPartida + Advalorem=row[27], # C30 - Advalorem TipoExpo='EXPO DEF', PedimentoR1=rectified_pedimento, - EDocument=row[39], # C40 - EDocument - NumOperacionVU=row[40], # C41 - NumOperacionVU + EDocument=row[37], # C40 - EDocument + NumOperacionVU=row[38], # C41 - NumOperacionVU Series=series_info, - Marca=row[42], # C43 - Marca - Modelo=row[43], # C44 - Modelo - FraccionAmericana=row[44], # C45 - FraccionAme - ECCN=row[45], # C46 - ECCN - FechaEmision=row[48], # C49 - FechaEmision + Marca=StringHelper.clean_text(row[40]), # C43 - Marca + Modelo=StringHelper.clean_text(row[41]), # C44 - Modelo + FraccionAmericana=row[42], # C45 - FraccionAme + ECCN=row[43], # C46 - ECCN + FechaEmision=parse_yyyymmdd_date(row[46]) if row[46] else None, # C49 - FechaEmision BaseDeDatos=filters.database_name, NumGafUni=driver_badge, - UsuarioCap=row[49], # C50 - UsuarioCap - UsuarioAcr=row[50], # C51 - UsuarioAct - Transportista=row[51], # C52 - Transportista - NumCaja=row[52], # C53 - Transporte + NumTrasporte - Pedimento18=row[53], # C54 - Pedimento18 - AduanaCru=row[32], # C33 - Aduana_Cruce - Lote=row[54] # C55 - Lote + UsuarioCap=row[47], # C50 - UsuarioCap + UsuarioAcr=row[48], # C51 - UsuarioAct + Transportista=row[49], # C52 - Carrier ID (derived from log.transport_id) + NumCaja=row[50], # C53 - log.transport_id || log.transport_num + Pedimento18=row[51], # C54 - empty + AduanaCru=row[30], # C33 - Aduana_Cruce + Lote=row[52] if len(row) > 52 else '' # C55 - Lote ) + movements.append(movement) @@ -342,7 +353,7 @@ class ExportService: logger.debug("Including cancelled invoices (include_cancelled = true)") # Date range - date_field = "ih.invoice_date" if filters.range_type.value == "FF" else "log.payment_date" + date_field = "ih.invoice_date" if filters.range_type.value == "FF" else "pd.payment_date" conditions.append(f"{date_field} >= TO_DATE('{filters.start_date}', 'YYYYMMDD')") conditions.append(f"{date_field} <= TO_DATE('{filters.end_date}', 'YYYYMMDD')") diff --git a/backend/api/v1/modules/a76/reports/movements/invoices/services/export_repair.py b/backend/api/v1/modules/a76/reports/movements/invoices/services/export_repair.py index 58743e80..98b33a0f 100644 --- a/backend/api/v1/modules/a76/reports/movements/invoices/services/export_repair.py +++ b/backend/api/v1/modules/a76/reports/movements/invoices/services/export_repair.py @@ -102,8 +102,18 @@ class ExportRepairService: consecutivo = row[15] # C35 - Consecutivo # Totals come directly from GROUP BY query (no N+1 problem) - total_me = row[24] # total_me - total_mn = row[25] # total_mn + def to_float(val): + if val is None or val == '': return 0.0 + try: return float(val) + except (ValueError, TypeError): return 0.0 + + total_me = to_float(row[24]) # total_me + total_mn = to_float(row[25]) # total_mn + sum_value_usd = to_float(row[27]) + sum_value_mxn = to_float(row[28]) + + total_me = sum_value_usd if sum_value_usd > 0 else total_me + total_mn = sum_value_mxn if sum_value_mxn > 0 else total_mn # Calculate exchange rate and value valor_comercial, tipo_cambio = ExchangeRateCalculator.calculate_for_aggregated( @@ -294,9 +304,9 @@ class ExportRepairService: TipoMovTemDef=row[33], # C34 - TipoFactura EsCambioRegimen='N', Regimen=row[7], # C8 - Regimen - Fecha_Inicio=row[8], # C9 - Fecha_Inicio - Fecha_Fin=row[9], # C10 - Fecha_Fin - Fecha_Pago=row[10], # C11 - Fecha_Pago + Fecha_Inicio=parse_yyyymmdd_date(row[8]), # C9 - Fecha_Inicio + Fecha_Fin=parse_yyyymmdd_date(row[9]), # C10 - Fecha_Fin + Fecha_Pago=parse_yyyymmdd_date(row[10]), # C11 - Fecha_Pago Remesa=row[11], # C12 - Remesa Proveedor=proveedor_info.get('name'), RFCProveedor=proveedor_info.get('rfc'), @@ -306,7 +316,7 @@ class ExportRepairService: VendidoATaxID=vendido_info.get('tax_id'), AgenteAduanal=agente_info.get('name'), Patente=agente_info.get('license'), - NumParte=row[17], # C18 - Clase (NumParte) + NumParte=row[46], # C47 - NumParte DescripcionE=StringHelper.clean_text(row[18]), # C19 DescripcionI=StringHelper.clean_text(row[19]), # C20 CantidadIE=float(row[20]) if row[20] else 0.0, # C21 @@ -332,7 +342,7 @@ class ExportRepairService: FraccionAmericana=row[44], # C45 - FraccionAme ECCN=row[45], # C46 - ECCN SimboloEx=simbolo_ex, - FechaEmision=row[48], # C49 - FechaFactura + FechaEmision=parse_yyyymmdd_date(row[48]) if row[48] else None, # C49 - FechaFactura BaseDeDatos=filters.database_name, NumGafUni=num_gaf_uni, UsuarioCap=row[49], # C50 - UsuarioCap @@ -341,7 +351,7 @@ class ExportRepairService: NumCaja=row[52], # C53 - Transporte + NumTrasporte Pedimento18=row[53], # C54 - Pedimento18 AduanaCru=row[32], # C33 - Aduana_Cruce - Lote=row[54] # C55 - Lote + Lote=row[54] if len(row) > 54 else '' # C55 - Lote ) movements.append(movement) @@ -369,7 +379,7 @@ class ExportRepairService: if filters.range_type.value == "FF": where_conditions.append(f"ih.invoice_date >= TO_DATE('{filters.start_date}', 'YYYYMMDD') AND ih.invoice_date <= TO_DATE('{filters.end_date}', 'YYYYMMDD')") else: - where_conditions.append(f"log.payment_date >= TO_DATE('{filters.start_date}', 'YYYYMMDD') AND log.payment_date <= TO_DATE('{filters.end_date}', 'YYYYMMDD')") + where_conditions.append(f"pd.payment_date >= TO_DATE('{filters.start_date}', 'YYYYMMDD') AND pd.payment_date <= TO_DATE('{filters.end_date}', 'YYYYMMDD')") # Provider filter if filters.provider: @@ -391,30 +401,6 @@ class ExportRepairService: return " AND ".join(where_conditions) - def _calculate_totals(self, db: Session, db_name: str, consecutivo: int, discharge_filter: str) -> tuple: - """Calculate totals for main partidas with discharge filter.""" - discharge_clause = "" - if discharge_filter == "SiDes": - discharge_clause = " AND il.is_discharged = true" - elif discharge_filter == "NoDes": - discharge_clause = " AND il.is_discharged = false" - - sql = text(f""" - SELECT - COALESCE(SUM(lf.value_usd), 0), - COALESCE(SUM(lf.value_mxn), 0) - FROM a76.item_line_financials lf - INNER JOIN a76.item_lines il ON il.id = lf.item_line_id - INNER JOIN a76.items itm ON itm.id = il.item_id - WHERE itm.invoice_id = :consecutivo - AND il.is_subpart = false - {discharge_clause} - """) - - result = db.execute(sql, {"consecutivo": consecutivo}).fetchone() - total_me = float(result[0]) if result and result[0] is not None else 0.0 - total_mn = float(result[1]) if result and result[1] is not None else 0.0 - return total_me, total_mn def _get_driver_badge(self, db: Session, db_name: str, factura: str) -> str: diff --git a/backend/api/v1/modules/a76/reports/movements/invoices/services/query_builders.py b/backend/api/v1/modules/a76/reports/movements/invoices/services/query_builders.py index b935e039..6e14c659 100644 --- a/backend/api/v1/modules/a76/reports/movements/invoices/services/query_builders.py +++ b/backend/api/v1/modules/a76/reports/movements/invoices/services/query_builders.py @@ -19,9 +19,9 @@ class TemporaryImportQueries: CASE WHEN ih.is_updated THEN 'AC' ELSE 'NA' END AS C4, COALESCE(ped.pedimento_code, '') AS C5, COALESCE(ped.regime, '') AS C10, - COALESCE(TO_CHAR(log.entry_exit_date, 'YYYYMMDD'), '') AS C11, - COALESCE(TO_CHAR(log.delivery_date, 'YYYYMMDD'), '') AS C12, - COALESCE(TO_CHAR(log.payment_date, 'YYYYMMDD'), '') AS C13, + COALESCE(TO_CHAR(pd.entry_date, 'YYYYMMDD'), '') AS C11, + COALESCE(TO_CHAR(pd.end_date, 'YYYYMMDD'), TO_CHAR(pd.payment_date, 'YYYYMMDD'), '') AS C12, + COALESCE(TO_CHAR(pd.payment_date, 'YYYYMMDD'), '') AS C13, COALESCE(cmp.remesa, 0) AS C14, COALESCE(fin.exchange_rate, 0) AS C15, COALESCE(cmp.provider_id::text, '') AS C16, @@ -49,7 +49,9 @@ class TemporaryImportQueries: COALESCE(fin.value_mn, 0) AS total_mn, COALESCE(lf_agg.sum_value_mxn, 0) AS valor_comercial_mn, COALESCE(lf_agg.sum_value_temp_mxn, 0) AS valor_mp_temp_mn, - COALESCE(lf_agg.sum_value_added_mxn, 0) AS valor_agre_mn + COALESCE(lf_agg.sum_value_added_mxn, 0) AS valor_agre_mn, + COALESCE(lf_agg.sum_value_temp_usd, 0) AS valor_mp_temp_usd, + COALESCE(lf_agg.sum_value_usd, 0) AS sum_value_usd FROM a76.invoice_header ih LEFT JOIN a76.invoice_compliance_mx cmp ON cmp.invoice_id = ih.id LEFT JOIN a76.invoice_financials fin ON fin.invoice_id = ih.id @@ -62,15 +64,17 @@ class TemporaryImportQueries: pro_rect.original_license = ped.license AND pro_rect.original_pedimento_number = ped.pedimento_number LEFT JOIN a76.pedimentos ped_r1 ON ped_r1.id = pro_rect.pedimento_id + LEFT JOIN a76.pedimento_dates pd ON pd.pedimento_id = ped.id LEFT JOIN ( - SELECT i.invoice_id, + SELECT il.invoice_id, SUM(COALESCE(lf.value_mxn, 0)) AS sum_value_mxn, SUM(COALESCE(lf.value_temp_material_mxn, 0)) AS sum_value_temp_mxn, - SUM(COALESCE(lf.value_added_mxn, 0)) AS sum_value_added_mxn - FROM a76.items i - JOIN a76.item_lines il ON il.item_id = i.id + SUM(COALESCE(lf.value_added_mxn, 0)) AS sum_value_added_mxn, + SUM(COALESCE(lf.value_temp_material_usd, 0)) AS sum_value_temp_usd, + SUM(COALESCE(lf.value_usd, 0)) AS sum_value_usd + FROM a76.item_lines il JOIN a76.item_line_financials lf ON lf.item_line_id = il.id - GROUP BY i.invoice_id + GROUP BY il.invoice_id ) lf_agg ON lf_agg.invoice_id = ih.id WHERE ih.operation_type = 'imp' AND ih.invoice_type = 'TEM' @@ -95,9 +99,9 @@ class TemporaryImportQueries: COALESCE(prov.name, '') AS C8, COALESCE(client.name, '') AS C9, COALESCE(ped.regime, '') AS C10, - COALESCE(TO_CHAR(log.entry_exit_date, 'YYYYMMDD'), '') AS C11, - COALESCE(TO_CHAR(log.delivery_date, 'YYYYMMDD'), '') AS C12, - COALESCE(TO_CHAR(log.payment_date, 'YYYYMMDD'), '') AS C13, + COALESCE(TO_CHAR(pd.entry_date, 'YYYYMMDD'), '') AS C11, + COALESCE(TO_CHAR(pd.end_date, 'YYYYMMDD'), TO_CHAR(pd.payment_date, 'YYYYMMDD'), '') AS C12, + COALESCE(TO_CHAR(pd.payment_date, 'YYYYMMDD'), '') AS C13, COALESCE(cmp.remesa, 0) AS C14, COALESCE(fin.exchange_rate, 0) AS C15, COALESCE(cmp.provider_id::text, '') AS C16, @@ -115,16 +119,16 @@ class TemporaryImportQueries: COALESCE(lf.customs_value_usd, 0) AS C28, COALESCE(lq.net_weight, 0) AS C29, COALESCE(lq.gross_weight, 0) AS C30, - COALESCE(ih.purchase_order, '') AS C31, + COALESCE(il.order, ih.purchase_order, '') AS C31, COALESCE(lc.fraction, '') AS C32, COALESCE(lc.fraction_type, '') AS C33, COALESCE(lc.advalorem_numeric, 0) AS C34, - COALESCE(lc.sector, '') AS C35, + COALESCE(NULLIF(lc.sector, ''), NULLIF(fap.sector, ''), '') AS C35, COALESCE(lf.igi_amount_usd, 0) AS C36, COALESCE(lc.origin_country, '') AS C37, COALESCE(cmp.aduana, '') AS C38, ih.id AS C39, - FALSE AS C40, + COALESCE(il.material_type, 'P') AS C40, COALESCE( ped_r1.year || '-' || ped_r1.customs_office || '-' || ped_r1.license || '-' || ped_r1.pedimento_number, pro_origin.original_pedimento_year || '-' || pro_origin.original_customs_office || '-' || pro_origin.original_license || '-' || pro_origin.original_pedimento_number, @@ -133,11 +137,11 @@ class TemporaryImportQueries: cmp.edocument AS C42, -- [41] cmp.vucem_operation_num AS C43, -- [42] COALESCE(il.line_number, 0) AS C44, - '' AS C45, - '' AS C46, + REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.brand, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' ') AS C45, + REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.model, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' ') AS C46, COALESCE(cls.us_fraction, '') AS C47, - COALESCE(prt.eccn, '') AS C48, - COALESCE(il.part_number::text, '') AS C49, + COALESCE(prt.eccn, fac.eccn_code, '') AS C48, + COALESCE(prt.part_number::text, '') AS C49, COALESCE(fin.exchange_rate, 0) AS C50, COALESCE(TO_CHAR(ih.emission_date, 'YYYYMMDD'), '') AS C51, COALESCE(ih.capture_user, '') AS C52, @@ -145,8 +149,10 @@ class TemporaryImportQueries: COALESCE(log.carrier_id, '') AS C54, COALESCE(log.transport_num || ' ' || log.license_plate, '') AS C55, '' AS C56, - '' AS C57, - '' AS C58 + COALESCE(ld.lot, '') AS C57, + '' AS C58, + COALESCE(lf.value_temp_material_mxn, 0) AS C59, + COALESCE(lf.value_temp_material_usd, 0) AS C60 FROM a76.invoice_header ih LEFT JOIN a76.invoice_compliance_mx cmp ON cmp.invoice_id = ih.id LEFT JOIN a76.invoice_financials fin ON fin.invoice_id = ih.id @@ -159,16 +165,18 @@ class TemporaryImportQueries: pro_rect.original_license = ped.license AND pro_rect.original_pedimento_number = ped.pedimento_number LEFT JOIN a76.pedimentos ped_r1 ON ped_r1.id = pro_rect.pedimento_id + LEFT JOIN a76.pedimento_dates pd ON pd.pedimento_id = ped.id LEFT JOIN a76.clients_and_providers prov ON prov.id = cmp.provider_id LEFT JOIN a76.clients_and_providers client ON client.id = cmp.sold_to_id - LEFT JOIN a76.items i ON i.invoice_id = ih.id - LEFT JOIN a76.item_lines il ON il.item_id = i.id + LEFT JOIN a76.item_lines il ON il.invoice_id = ih.id LEFT JOIN a76.item_line_descriptions ld ON ld.item_line_id = il.id LEFT JOIN a76.item_line_quantities lq ON lq.item_line_id = il.id LEFT JOIN a76.item_line_financials lf ON lf.item_line_id = il.id LEFT JOIN a76.item_line_customs lc ON lc.item_line_id = il.id LEFT JOIN a76.classes cls ON cls.id = il.class_id - LEFT JOIN a76.parts prt ON prt.id = il.part_number + LEFT JOIN a24.fa_classes fac ON fac.class_id = cls.id + LEFT JOIN a76.parts prt ON prt.id = il.part_number_id + LEFT JOIN a24.fa_partes fap ON fap.id = prt.id LEFT JOIN a76.units_of_measure um ON um.id = il.unit_of_measure WHERE ih.operation_type = 'imp' AND ih.invoice_type = 'TEM' @@ -225,9 +233,9 @@ class DefinitiveImportQueries: CASE WHEN ih.is_updated THEN 'AC' ELSE 'NA' END AS C4, COALESCE(ped.pedimento_code, '') AS C5, COALESCE(ped.regime, '') AS C10, - COALESCE(TO_CHAR(log.entry_exit_date, 'YYYYMMDD'), '') AS C11, - COALESCE(TO_CHAR(log.delivery_date, 'YYYYMMDD'), '') AS C12, - COALESCE(TO_CHAR(log.payment_date, 'YYYYMMDD'), '') AS C13, + COALESCE(TO_CHAR(pd.entry_date, 'YYYYMMDD'), '') AS C11, + COALESCE(TO_CHAR(pd.end_date, 'YYYYMMDD'), TO_CHAR(pd.payment_date, 'YYYYMMDD'), '') AS C12, + COALESCE(TO_CHAR(pd.payment_date, 'YYYYMMDD'), '') AS C13, COALESCE(log.payment_receipt_num, '') AS C14, COALESCE(fin.exchange_rate, 0) AS C15, COALESCE(cmp.provider_id::text, '') AS C16, @@ -252,7 +260,9 @@ class DefinitiveImportQueries: '' AS C58, '' AS C59, COALESCE(fin.value_me, 0) AS total_me, - COALESCE(fin.value_mn, 0) AS total_mn + COALESCE(fin.value_mn, 0) AS total_mn, + COALESCE(lf_agg.sum_value_usd, 0) AS sum_value_usd, + COALESCE(lf_agg.sum_value_mxn, 0) AS sum_value_mxn FROM a76.invoice_header ih LEFT JOIN a76.invoice_compliance_mx cmp ON cmp.invoice_id = ih.id LEFT JOIN a76.invoice_financials fin ON fin.invoice_id = ih.id @@ -265,6 +275,16 @@ class DefinitiveImportQueries: pro_rect.original_license = ped.license AND pro_rect.original_pedimento_number = ped.pedimento_number LEFT JOIN a76.pedimentos ped_r1 ON ped_r1.id = pro_rect.pedimento_id + LEFT JOIN a76.pedimento_dates pd ON pd.pedimento_id = ped.id + LEFT JOIN ( + SELECT il.invoice_id, + SUM(CASE WHEN COALESCE(fil.is_subitem, false) THEN 0 ELSE COALESCE(lf.value_mxn, 0) END) AS sum_value_mxn, + SUM(CASE WHEN COALESCE(fil.is_subitem, false) THEN 0 ELSE COALESCE(lf.value_usd, 0) END) AS sum_value_usd + FROM a76.item_lines il + JOIN a76.item_line_financials lf ON lf.item_line_id = il.id + LEFT JOIN a24.fa_item_lines fil ON fil.id = il.id + GROUP BY il.invoice_id + ) lf_agg ON lf_agg.invoice_id = ih.id WHERE ih.operation_type = 'imp' AND ih.invoice_type IN ('DEF', 'EXDEF', 'MATDE') AND {where_clause} @@ -273,70 +293,73 @@ class DefinitiveImportQueries: @staticmethod def build_main_query(db_name: str, where_clause: str) -> str: + """Build main SQL query for DETAILED mode (all partidas) from PostgreSQL for definitive imports.""" return f""" SELECT - ih.invoice_number AS C1, -- [0] - ped.pedimento_number AS C2, -- [1] - ih.invoice_date AS C3, -- [2] - ped.status AS C4, -- [3] - ped.pedimento_code AS C5, -- [4] - '' AS C6, -- [5] - '' AS C7, -- [6] - COALESCE(prov.name, '') AS C8, -- [7] Provider name - COALESCE(client.name, '') AS C9, -- [8] Client name - ped.regime AS C10, -- [9] - log.entry_exit_date AS C11, -- [10] - log.delivery_date AS C12, -- [11] - log.payment_date AS C13, -- [12] - log.payment_receipt_num AS C14, -- [13] - '' AS C15, -- [14] - cmp.provider_id AS C16, -- [15] - cmp.sold_to_id AS C17, -- [16] - cmp.customs_broker_id AS C18, -- [17] - '' AS C19, -- [18] - prt.part_number AS C20, -- [19] - ld.description_spanish AS C21, -- [20] - ld.description_english AS C22, -- [21] - lq.quantity AS C23, -- [22] - um.code AS C24, -- [23] - lf.value_mxn AS C25, -- [24] - '' AS C26, -- [25] - lf.value_usd AS C27, -- [26] - '' AS C28, -- [27] - lq.net_weight AS C29, -- [28] - lq.gross_weight AS C30, -- [29] - ih.purchase_order AS C31, -- [30] - lc.fraction AS C32, -- [31] - '' AS C33, '' AS C34, -- [32-33] - ih.id AS C35, -- [34] - '' AS C36, '' AS C37, -- [35-36] - lc.origin_country AS C38, -- [37] - cmp.aduana AS C39, -- [38] - il.material_type AS C40, -- [39] - il.id AS C41, -- [40] + ih.invoice_number AS C1, + COALESCE(ped.pedimento_number, '') AS C2, + TO_CHAR(ih.invoice_date, 'YYYYMMDD') AS C3, + CASE WHEN ih.is_updated THEN 'AC' ELSE 'NA' END AS C4, + COALESCE(ped.pedimento_code, '') AS C5, + COALESCE(fin.value_me, 0) AS C6, + COALESCE(fin.value_mn, 0) AS C7, + COALESCE(prov.name, '') AS C8, + COALESCE(client.name, '') AS C9, + COALESCE(ped.regime, '') AS C10, + COALESCE(TO_CHAR(pd.entry_date, 'YYYYMMDD'), '') AS C11, + COALESCE(TO_CHAR(pd.end_date, 'YYYYMMDD'), TO_CHAR(pd.payment_date, 'YYYYMMDD'), '') AS C12, + COALESCE(TO_CHAR(pd.payment_date, 'YYYYMMDD'), '') AS C13, + COALESCE(log.payment_receipt_num, '') AS C14, + COALESCE(fin.exchange_rate, 0) AS C15, + COALESCE(cmp.provider_id::text, '') AS C16, + COALESCE(cmp.sold_to_id::text, '') AS C17, + COALESCE(cmp.customs_broker_id::text, '') AS C18, + '' AS C19, + COALESCE(il.class_id::text, '') AS C20, + REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.description_spanish, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' ') AS C21, + REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.description_english, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' ') AS C22, + COALESCE(lq.quantity, 0) AS C23, + COALESCE(um.code, '') AS C24, + COALESCE(lf.value_mxn, 0) AS C25, + COALESCE(lf.customs_value_mxn, 0) AS C26, + COALESCE(lf.value_usd, 0) AS C27, + COALESCE(lf.customs_value_usd, 0) AS C28, + COALESCE(lq.net_weight, 0) AS C29, + COALESCE(lq.gross_weight, 0) AS C30, + COALESCE(il.order, ih.purchase_order, '') AS C31, + COALESCE(lc.fraction, '') AS C32, + COALESCE(lc.fraction_type, '') AS C33, + COALESCE(lc.advalorem_numeric, 0) AS C34, + COALESCE(NULLIF(lc.sector, ''), NULLIF(fap.sector, ''), '') AS C35, + COALESCE(lf.igi_amount_usd, 0) AS C36, + COALESCE(lc.origin_country, '') AS C37, + COALESCE(cmp.aduana, '') AS C38, + ih.id AS C39, + COALESCE(il.material_type, 'P') AS C40, COALESCE( ped_r1.year || '-' || ped_r1.customs_office || '-' || ped_r1.license || '-' || ped_r1.pedimento_number, pro_origin.original_pedimento_year || '-' || pro_origin.original_customs_office || '-' || pro_origin.original_license || '-' || pro_origin.original_pedimento_number, '' - ) AS C42, -- [41] rectification_id - cmp.edocument AS C43, -- [42] - cmp.vucem_operation_num AS C44, -- [43] - il.line_number AS C45, -- [44] - ld.brand AS C46, -- [45] - ld.model AS C47, -- [46] - prt.us_fraction AS C48, -- [47] - prt.eccn AS C49, -- [48] - prt.id AS C50, -- [49] - fin.exchange_rate AS C51, -- [50] - ih.emission_date AS C52, -- [51] - ih.capture_user AS C53, -- [52] - ih.who_updated AS C54, -- [53] - '' AS C55, -- [54] - COALESCE(log.transport_id || ' ' || log.transport_num, '') AS C56, -- [55] - '' AS C57, -- [56] Pedimento18 (row[56]) - COALESCE(ld.lot, '') AS C58, -- [57] Lote (row[57]) - '' AS C59, -- [58] TipoPed (row[58]) - '' AS C60 -- [59] Relleno final + ) AS C41, + cmp.edocument AS C42, + cmp.vucem_operation_num AS C43, + COALESCE(il.line_number, 0) AS C44, + REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.brand, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' ') AS C45, + REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.model, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' ') AS C46, + COALESCE(cls.us_fraction, '') AS C47, + COALESCE(prt.eccn, fac.eccn_code, '') AS C48, + COALESCE(prt.part_number::text, '') AS C49, + COALESCE(fin.exchange_rate, 0) AS C50, + COALESCE(TO_CHAR(ih.emission_date, 'YYYYMMDD'), '') AS C51, + COALESCE(ih.capture_user, '') AS C52, + COALESCE(ih.who_updated, '') AS C53, + COALESCE(log.carrier_id, '') AS C54, + COALESCE(log.transport_id || ' ' || log.transport_num, '') AS C55, + '' AS C56, + COALESCE(ld.lot, '') AS C57, + '' AS C58, + 0 AS C59, + 0 AS C60 FROM a76.invoice_header ih LEFT JOIN a76.invoice_compliance_mx cmp ON cmp.invoice_id = ih.id LEFT JOIN a76.invoice_financials fin ON fin.invoice_id = ih.id @@ -349,16 +372,18 @@ class DefinitiveImportQueries: pro_rect.original_license = ped.license AND pro_rect.original_pedimento_number = ped.pedimento_number LEFT JOIN a76.pedimentos ped_r1 ON ped_r1.id = pro_rect.pedimento_id + LEFT JOIN a76.pedimento_dates pd ON pd.pedimento_id = ped.id LEFT JOIN a76.clients_and_providers prov ON prov.id = cmp.provider_id LEFT JOIN a76.clients_and_providers client ON client.id = cmp.sold_to_id - LEFT JOIN a76.items itm ON itm.invoice_id = ih.id - LEFT JOIN a76.item_lines il ON il.item_id = itm.id + LEFT JOIN a76.item_lines il ON il.invoice_id = ih.id LEFT JOIN a76.item_line_descriptions ld ON ld.item_line_id = il.id LEFT JOIN a76.item_line_quantities lq ON lq.item_line_id = il.id LEFT JOIN a76.item_line_financials lf ON lf.item_line_id = il.id LEFT JOIN a76.item_line_customs lc ON lc.item_line_id = il.id LEFT JOIN a76.classes cls ON cls.id = il.class_id - LEFT JOIN a76.parts prt ON prt.id = il.part_number + LEFT JOIN a24.fa_classes fac ON fac.class_id = cls.id + LEFT JOIN a76.parts prt ON prt.id = il.part_number_id + LEFT JOIN a24.fa_partes fap ON fap.id = prt.id LEFT JOIN a76.units_of_measure um ON um.id = il.unit_of_measure WHERE {where_clause} ORDER BY ih.invoice_number, il.line_number @@ -374,8 +399,7 @@ class DefinitiveImportQueries: FROM a76.item_line_financials lf INNER JOIN a76.item_lines il ON il.id = lf.item_line_id LEFT JOIN a24.fa_item_lines fil ON fil.id = il.id - INNER JOIN a76.items itm ON itm.id = il.item_id - WHERE itm.invoice_id = :consecutivo + WHERE il.invoice_id = :consecutivo """ @@ -425,7 +449,7 @@ class RepairImportQueries: CASE WHEN ih.is_updated THEN 'AC' ELSE 'NA' END AS C5, COALESCE(ped.pedimento_code, '') AS C6, COALESCE(ped.regime, '') AS C7, - COALESCE(TO_CHAR(log.payment_date, 'YYYYMMDD'), '') AS C9, + COALESCE(TO_CHAR(pd.payment_date, 'YYYYMMDD'), '') AS C9, COALESCE(cmp.remesa::text, '') AS C10, COALESCE(fin.exchange_rate, 0) AS C11, COALESCE(cmp.provider_id::text, '') AS C12, @@ -449,7 +473,9 @@ class RepairImportQueries: ped_r1.year || '-' || ped_r1.customs_office || '-' || ped_r1.license || '-' || ped_r1.pedimento_number, pro_origin.original_pedimento_year || '-' || pro_origin.original_customs_office || '-' || pro_origin.original_license || '-' || pro_origin.original_pedimento_number, '' - ) AS C48 + ) AS C48, + COALESCE(lf_agg.sum_value_usd, 0) AS sum_value_usd, + COALESCE(lf_agg.sum_value_mxn, 0) AS sum_value_mxn FROM a76.invoice_header ih LEFT JOIN a76.invoice_compliance_mx cmp ON cmp.invoice_id = ih.id LEFT JOIN a76.invoice_financials fin ON fin.invoice_id = ih.id @@ -462,13 +488,23 @@ class RepairImportQueries: pro_rect.original_license = ped.license AND pro_rect.original_pedimento_number = ped.pedimento_number LEFT JOIN a76.pedimentos ped_r1 ON ped_r1.id = pro_rect.pedimento_id + LEFT JOIN a76.pedimento_dates pd ON pd.pedimento_id = ped.id + LEFT JOIN ( + SELECT il.invoice_id, + SUM(CASE WHEN COALESCE(fil.is_subitem, false) THEN 0 ELSE COALESCE(lf.value_mxn, 0) END) AS sum_value_mxn, + SUM(CASE WHEN COALESCE(fil.is_subitem, false) THEN 0 ELSE COALESCE(lf.value_usd, 0) END) AS sum_value_usd + FROM a76.item_lines il + JOIN a76.item_line_financials lf ON lf.item_line_id = il.id + LEFT JOIN a24.fa_item_lines fil ON fil.id = il.id + WHERE 1=1 {discharge_filter} + GROUP BY il.invoice_id + ) lf_agg ON lf_agg.invoice_id = ih.id WHERE ih.operation_type = 'imp' AND COALESCE(cmp.is_regime_change, false) = false AND EXISTS ( - SELECT 1 FROM a76.items i2 - INNER JOIN a76.item_lines il2 ON il2.item_id = i2.id + SELECT 1 FROM a76.item_lines il2 INNER JOIN a24.fa_item_lines fil2 ON fil2.id = il2.id - WHERE i2.invoice_id = ih.id AND fil2.search_invoice IS NOT NULL + WHERE il2.invoice_id = ih.id AND fil2.search_invoice IS NOT NULL {discharge_filter} ) {"AND " + where_str if where_str else ""} @@ -494,14 +530,15 @@ class RepairImportQueries: CASE WHEN ih.is_updated THEN 'AC' ELSE 'NA' END, COALESCE(ped.pedimento_code, ''), COALESCE(ped.regime, ''), - '', - COALESCE(TO_CHAR(log.payment_date, 'YYYYMMDD'), ''), + COALESCE(TO_CHAR(pd.entry_date, 'YYYYMMDD'), ''), + COALESCE(TO_CHAR(pd.end_date, 'YYYYMMDD'), TO_CHAR(pd.payment_date, 'YYYYMMDD'), ''), + COALESCE(TO_CHAR(pd.payment_date, 'YYYYMMDD'), ''), COALESCE(cmp.remesa::text, ''), COALESCE(fin.exchange_rate, 0), COALESCE(cmp.provider_id::text, ''), COALESCE(cmp.sold_to_id::text, ''), COALESCE(cmp.customs_broker_id::text, ''), - COALESCE(il.part_number::text, ''), + COALESCE(prt.part_number::text, ''), REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.description_spanish, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' '), REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.description_english, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' '), COALESCE(lq.quantity, 0), @@ -510,14 +547,14 @@ class RepairImportQueries: COALESCE(lf.value_usd, 0), COALESCE(lq.net_weight, 0), COALESCE(lq.gross_weight, 0), - COALESCE(ih.purchase_order, ''), + COALESCE(il.order, ih.purchase_order, ''), COALESCE(lc.fraction, ''), '', - COALESCE(lc.sector, ''), + COALESCE(NULLIF(lc.sector, ''), NULLIF(fap.sector, ''), ''), COALESCE(lc.origin_country, ''), COALESCE(ped.customs_office, ''), ih.id, - 'P', + COALESCE(il.material_type, 'P'), COALESCE( ped_r1.year || '-' || ped_r1.customs_office || '-' || ped_r1.license || '-' || ped_r1.pedimento_number, pro_origin.original_pedimento_year || '-' || pro_origin.original_customs_office || '-' || pro_origin.original_license || '-' || pro_origin.original_pedimento_number, @@ -528,7 +565,7 @@ class RepairImportQueries: REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.brand, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' '), REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.model, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' '), COALESCE(lc.american_fraction, ''), - COALESCE(prt.eccn, ''), + COALESCE(prt.eccn, fac.eccn_code, ''), COALESCE(fin.exchange_rate, 0), COALESCE(TO_CHAR(ih.emission_date, 'YYYYMMDD'), ''), COALESCE(ih.capture_user, ''), @@ -549,14 +586,17 @@ class RepairImportQueries: pro_rect.original_license = ped.license AND pro_rect.original_pedimento_number = ped.pedimento_number LEFT JOIN a76.pedimentos ped_r1 ON ped_r1.id = pro_rect.pedimento_id - LEFT JOIN a76.items itm ON itm.invoice_id = ih.id - LEFT JOIN a76.item_lines il ON il.item_id = itm.id + LEFT JOIN a76.pedimento_dates pd ON pd.pedimento_id = ped.id + LEFT JOIN a76.item_lines il ON il.invoice_id = ih.id LEFT JOIN a76.item_line_descriptions ld ON ld.item_line_id = il.id LEFT JOIN a76.item_line_quantities lq ON lq.item_line_id = il.id LEFT JOIN a76.item_line_financials lf ON lf.item_line_id = il.id LEFT JOIN a76.item_line_customs lc ON lc.item_line_id = il.id LEFT JOIN a24.fa_item_lines fil ON fil.id = il.id - LEFT JOIN a76.parts prt ON prt.id = il.part_number + LEFT JOIN a76.classes cls ON cls.id = il.class_id + LEFT JOIN a24.fa_classes fac ON fac.class_id = cls.id + LEFT JOIN a76.parts prt ON prt.id = il.part_number_id + LEFT JOIN a24.fa_partes fap ON fap.id = prt.id LEFT JOIN a76.units_of_measure um ON um.id = il.unit_of_measure WHERE ih.operation_type = 'imp' AND COALESCE(cmp.is_regime_change, false) = false @@ -583,8 +623,7 @@ class RepairImportQueries: FROM a76.item_line_financials lf INNER JOIN a76.item_lines il ON il.id = lf.item_line_id LEFT JOIN a24.fa_item_lines fil ON fil.id = il.id - INNER JOIN a76.items itm ON itm.id = il.item_id - WHERE itm.invoice_id = :consecutivo + WHERE il.invoice_id = :consecutivo {discharge_filter} """ @@ -628,9 +667,8 @@ class ExportQueries: CASE WHEN ih.is_updated THEN 'AC' ELSE 'NA' END AS C6, COALESCE(ped.pedimento_code, '') AS C7, COALESCE(ped.regime, '') AS C8, - COALESCE(TO_CHAR(log.entry_exit_date, 'YYYYMMDD'), '') AS C9, - COALESCE(TO_CHAR(log.delivery_date, 'YYYYMMDD'), '') AS C10, - COALESCE(TO_CHAR(log.payment_date, 'YYYYMMDD'), '') AS C11, + COALESCE(TO_CHAR(pd.end_date, 'YYYYMMDD'), TO_CHAR(pd.payment_date, 'YYYYMMDD'), '') AS C10, + COALESCE(TO_CHAR(pd.payment_date, 'YYYYMMDD'), '') AS C11, COALESCE(log.payment_receipt_num, '') AS C12, COALESCE(cmp.provider_id::text, '') AS C14, COALESCE(cmp.sold_to_id::text, '') AS C15, @@ -652,7 +690,9 @@ class ExportQueries: ped_r1.year || '-' || ped_r1.customs_office || '-' || ped_r1.license || '-' || ped_r1.pedimento_number, pro_origin.original_pedimento_year || '-' || pro_origin.original_customs_office || '-' || pro_origin.original_license || '-' || pro_origin.original_pedimento_number, '' - ) AS C54 + ) AS C54, + COALESCE(lf_agg.sum_value_usd, 0) AS sum_value_usd, + COALESCE(lf_agg.sum_value_mxn, 0) AS sum_value_mxn FROM a76.invoice_header ih LEFT JOIN a76.invoice_compliance_mx cmp ON cmp.invoice_id = ih.id LEFT JOIN a76.invoice_financials fin ON fin.invoice_id = ih.id @@ -665,6 +705,16 @@ class ExportQueries: pro_rect.original_license = ped.license AND pro_rect.original_pedimento_number = ped.pedimento_number LEFT JOIN a76.pedimentos ped_r1 ON ped_r1.id = pro_rect.pedimento_id + LEFT JOIN a76.pedimento_dates pd ON pd.pedimento_id = ped.id + LEFT JOIN ( + SELECT il.invoice_id, + SUM(CASE WHEN COALESCE(fil.is_subitem, false) THEN 0 ELSE COALESCE(lf.value_mxn, 0) END) AS sum_value_mxn, + SUM(CASE WHEN COALESCE(fil.is_subitem, false) THEN 0 ELSE COALESCE(lf.value_usd, 0) END) AS sum_value_usd + FROM a76.item_lines il + JOIN a76.item_line_financials lf ON lf.item_line_id = il.id + LEFT JOIN a24.fa_item_lines fil ON fil.id = il.id + GROUP BY il.invoice_id + ) lf_agg ON lf_agg.invoice_id = ih.id WHERE {where_clause} ORDER BY ih.invoice_number """ @@ -677,34 +727,32 @@ class ExportQueries: ped.pedimento_number AS C2, -- [1] ih.invoice_date AS C3, -- [2] '' AS C4, -- [3] - '' AS C5, -- [4] - ped.status AS C6, -- [5] - ped.pedimento_code AS C7, -- [6] - ped.regime AS C8, -- [7] - log.entry_exit_date AS C9, -- [8] - log.delivery_date AS C10, -- [9] - log.payment_date AS C11, -- [10] + ped.pedimento_code AS C5, -- [4] + ped.regime AS C6, -- [5] + pd.entry_date AS C7, -- [6] + COALESCE(pd.end_date, pd.payment_date) AS C8, -- [7] + pd.payment_date AS C9, -- [8] log.payment_receipt_num AS C12, -- [11] '' AS C13, -- [12] cmp.provider_id AS C14, -- [13] cmp.sold_to_id AS C15, -- [14] cmp.customs_broker_id AS C16, -- [15] '' AS C17, -- [16] - prt.part_number AS C18, -- [17] - ld.description_spanish AS C19, -- [18] - ld.description_english AS C20, -- [19] + COALESCE(prt.part_number::text, '') AS C18, -- [17] + REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.description_spanish, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' ') AS C19, -- [18] + REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.description_english, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' ') AS C20, -- [19] lq.quantity AS C21, -- [20] um.code AS C22, -- [21] '' AS C23, -- [22] '' AS C24, -- [23] - lq.net_weight AS C25, -- [24] - lq.gross_weight AS C26, -- [25] - ih.purchase_order AS C27, -- [26] - lc.fraction AS C28, -- [27] - '' AS C29, -- [28] - '' AS C30, -- [29] - '' AS C31, -- [30] - '' AS C32, -- [31] + COALESCE(lq.net_weight, 0) AS C25, -- [24] + COALESCE(lq.gross_weight, 0) AS C26, -- [25] + COALESCE(il.order, ih.purchase_order, '') AS C27, -- [26] + COALESCE(lc.fraction, '') AS C28, -- [27] + COALESCE(lc.fraction_type, '') AS C29, -- [28] + COALESCE(lc.advalorem_numeric, 0) AS C30, -- [29] + COALESCE(NULLIF(lc.sector, ''), NULLIF(fap.sector, ''), '') AS C31, -- [30] + COALESCE(lc.origin_country, '') AS C32, -- [31] cmp.aduana AS C33, -- [32] ih.invoice_type AS C34, -- [33] ih.id AS C35, -- [34] @@ -719,10 +767,10 @@ class ExportQueries: cmp.edocument AS C40, -- [39] cmp.vucem_operation_num AS C41, -- [40] il.line_number AS C42, -- [41] - ld.brand AS C43, -- [42] - ld.model AS C44, -- [43] - prt.us_fraction AS C45, -- [44] - prt.eccn AS C46, -- [45] + REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.brand, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' ') AS C43, -- [42] + REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.model, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' ') AS C44, -- [43] + COALESCE(cls.us_fraction, '') AS C45, -- [44] + COALESCE(prt.eccn, fac.eccn_code, '') AS C46, -- [45] prt.id AS C47, -- [46] fin.exchange_rate AS C48, -- [47] ih.emission_date AS C49, -- [48] @@ -749,14 +797,16 @@ class ExportQueries: pro_rect.original_license = ped.license AND pro_rect.original_pedimento_number = ped.pedimento_number LEFT JOIN a76.pedimentos ped_r1 ON ped_r1.id = pro_rect.pedimento_id - LEFT JOIN a76.items itm ON itm.invoice_id = ih.id - LEFT JOIN a76.item_lines il ON il.item_id = itm.id + LEFT JOIN a76.pedimento_dates pd ON pd.pedimento_id = ped.id + LEFT JOIN a76.item_lines il ON il.invoice_id = ih.id LEFT JOIN a76.item_line_descriptions ld ON ld.item_line_id = il.id LEFT JOIN a76.item_line_quantities lq ON lq.item_line_id = il.id LEFT JOIN a76.item_line_financials lf ON lf.item_line_id = il.id LEFT JOIN a76.item_line_customs lc ON lc.item_line_id = il.id LEFT JOIN a76.classes cls ON cls.id = il.class_id - LEFT JOIN a76.parts prt ON prt.id = il.part_number + LEFT JOIN a24.fa_classes fac ON fac.class_id = cls.id + LEFT JOIN a76.parts prt ON prt.id = il.part_number_id + LEFT JOIN a24.fa_partes fap ON fap.id = prt.id LEFT JOIN a76.units_of_measure um ON um.id = il.unit_of_measure WHERE {where_clause} ORDER BY ih.invoice_number, il.line_number @@ -782,8 +832,7 @@ class ExportQueries: FROM a76.item_line_financials lf INNER JOIN a76.item_lines il ON il.id = lf.item_line_id LEFT JOIN a24.fa_item_lines fil ON fil.id = il.id - INNER JOIN a76.items itm ON itm.id = il.item_id - WHERE itm.invoice_id = :consecutivo + WHERE il.invoice_id = :consecutivo {discharge_filter} """ @@ -828,7 +877,7 @@ class ExportRepairQueries: CASE WHEN ih.is_updated THEN 'AC' ELSE 'NA' END AS C6, COALESCE(ped.pedimento_code, '') AS C7, COALESCE(ped.regime, '') AS C8, - COALESCE(TO_CHAR(log.payment_date, 'YYYYMMDD'), '') AS C11, + COALESCE(TO_CHAR(pd.payment_date, 'YYYYMMDD'), '') AS C11, COALESCE(cmp.remesa::text, '') AS C12, COALESCE(fin.exchange_rate, 0) AS C13, COALESCE(cmp.provider_id::text, '') AS C14, @@ -852,7 +901,9 @@ class ExportRepairQueries: ped_r1.year || '-' || ped_r1.customs_office || '-' || ped_r1.license || '-' || ped_r1.pedimento_number, pro_origin.original_pedimento_year || '-' || pro_origin.original_customs_office || '-' || pro_origin.original_license || '-' || pro_origin.original_pedimento_number, '' - ) AS C54 + ) AS C54, + COALESCE(lf_agg.sum_value_usd, 0) AS sum_value_usd, + COALESCE(lf_agg.sum_value_mxn, 0) AS sum_value_mxn FROM a76.invoice_header ih LEFT JOIN a76.invoice_compliance_mx cmp ON cmp.invoice_id = ih.id LEFT JOIN a76.invoice_financials fin ON fin.invoice_id = ih.id @@ -865,6 +916,17 @@ class ExportRepairQueries: pro_rect.original_license = ped.license AND pro_rect.original_pedimento_number = ped.pedimento_number LEFT JOIN a76.pedimentos ped_r1 ON ped_r1.id = pro_rect.pedimento_id + LEFT JOIN a76.pedimento_dates pd ON pd.pedimento_id = ped.id + LEFT JOIN ( + SELECT il.invoice_id, + SUM(CASE WHEN COALESCE(fil.is_subitem, false) THEN 0 ELSE COALESCE(lf.value_mxn, 0) END) AS sum_value_mxn, + SUM(CASE WHEN COALESCE(fil.is_subitem, false) THEN 0 ELSE COALESCE(lf.value_usd, 0) END) AS sum_value_usd + FROM a76.item_lines il + JOIN a76.item_line_financials lf ON lf.item_line_id = il.id + LEFT JOIN a24.fa_item_lines fil ON fil.id = il.id + WHERE 1=1 {discharge_filter} + GROUP BY il.invoice_id + ) lf_agg ON lf_agg.invoice_id = ih.id WHERE ih.operation_type = 'exp' AND ih.invoice_type = 'REPAR' {"AND " + where_str if where_str else ""} @@ -884,9 +946,9 @@ class ExportRepairQueries: CASE WHEN ih.is_updated THEN 'AC' ELSE 'NA' END AS C6, COALESCE(ped.pedimento_code, '') AS C7, COALESCE(ped.regime, '') AS C8, - '' AS C9, - '' AS C10, - COALESCE(TO_CHAR(log.payment_date, 'YYYYMMDD'), '') AS C11, + COALESCE(TO_CHAR(pd.entry_date, 'YYYYMMDD'), '') AS C9, + COALESCE(TO_CHAR(pd.end_date, 'YYYYMMDD'), TO_CHAR(pd.payment_date, 'YYYYMMDD'), '') AS C10, + COALESCE(TO_CHAR(pd.payment_date, 'YYYYMMDD'), '') AS C11, COALESCE(cmp.remesa::text, '') AS C12, COALESCE(fin.exchange_rate, 0) AS C13, COALESCE(cmp.provider_id::text, '') AS C14, @@ -902,18 +964,18 @@ class ExportRepairQueries: COALESCE(lf.customs_value_usd, 0) AS C24, COALESCE(lq.net_weight, 0) AS C25, COALESCE(lq.gross_weight, 0) AS C26, - COALESCE(ih.purchase_order, '') AS C27, + COALESCE(il.order, ih.purchase_order, '') AS C27, COALESCE(lc.fraction, '') AS C28, COALESCE(lc.fraction_type, '') AS C29, COALESCE(lc.advalorem_numeric, 0) AS C30, - COALESCE(lc.sector, '') AS C31, + COALESCE(NULLIF(lc.sector, ''), NULLIF(fap.sector, ''), '') AS C31, COALESCE(lc.origin_country, '') AS C32, COALESCE(ped.customs_office, '') AS C33, COALESCE(ih.document_type, '') AS C34, ih.id AS C35, COALESCE(lf.value_mxn, 0) AS C36, COALESCE(lf.value_usd, 0) AS C37, - 'P' AS C38, + COALESCE(il.material_type, 'P') AS C38, COALESCE( ped_r1.year || '-' || ped_r1.customs_office || '-' || ped_r1.license || '-' || ped_r1.pedimento_number, pro_origin.original_pedimento_year || '-' || pro_origin.original_customs_office || '-' || pro_origin.original_license || '-' || pro_origin.original_pedimento_number, @@ -925,8 +987,8 @@ class ExportRepairQueries: REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.brand, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' ') AS C43, REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.model, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' ') AS C44, COALESCE(cls.us_fraction, '') AS C45, - COALESCE(prt.eccn, '') AS C46, - COALESCE(il.part_number::text, '') AS C47, + COALESCE(prt.eccn, fac.eccn_code, '') AS C46, + COALESCE(prt.part_number::text, '') AS C47, COALESCE(fin.exchange_rate, 0) AS C48, COALESCE(TO_CHAR(ih.invoice_date, 'YYYYMMDD'), '') AS C49, COALESCE(ih.capture_user, '') AS C50, @@ -951,14 +1013,16 @@ class ExportRepairQueries: pro_rect.original_license = ped.license AND pro_rect.original_pedimento_number = ped.pedimento_number LEFT JOIN a76.pedimentos ped_r1 ON ped_r1.id = pro_rect.pedimento_id - LEFT JOIN a76.items itm ON itm.invoice_id = ih.id - LEFT JOIN a76.item_lines il ON il.item_id = itm.id + LEFT JOIN a76.pedimento_dates pd ON pd.pedimento_id = ped.id + LEFT JOIN a76.item_lines il ON il.invoice_id = ih.id LEFT JOIN a76.item_line_descriptions ld ON ld.item_line_id = il.id LEFT JOIN a76.item_line_quantities lq ON lq.item_line_id = il.id LEFT JOIN a76.item_line_financials lf ON lf.item_line_id = il.id LEFT JOIN a76.item_line_customs lc ON lc.item_line_id = il.id LEFT JOIN a76.classes cls ON cls.id = il.class_id - LEFT JOIN a76.parts prt ON prt.id = il.part_number + LEFT JOIN a24.fa_classes fac ON fac.class_id = cls.id + LEFT JOIN a76.parts prt ON prt.id = il.part_number_id + LEFT JOIN a24.fa_partes fap ON fap.id = prt.id LEFT JOIN a76.units_of_measure um ON um.id = il.unit_of_measure WHERE UPPER(ih.operation_type) IN ('EXP', 'TRA', 'RET') AND UPPER(ih.invoice_type) IN ('DEF', 'REPAR', 'EXDEF', 'MATDE') @@ -983,7 +1047,7 @@ class ExportRepairQueries: FROM a76.item_line_financials lf INNER JOIN a76.item_lines il ON il.id = lf.item_line_id LEFT JOIN a24.fa_item_lines fil ON il.id = lf.item_line_id - WHERE itm.invoice_id = :consecutivo + WHERE il.invoice_id = :consecutivo {discharge_filter} """ diff --git a/backend/api/v1/modules/a76/reports/movements/invoices/services/repair.py b/backend/api/v1/modules/a76/reports/movements/invoices/services/repair.py index df28b3c6..bf89f124 100644 --- a/backend/api/v1/modules/a76/reports/movements/invoices/services/repair.py +++ b/backend/api/v1/modules/a76/reports/movements/invoices/services/repair.py @@ -93,6 +93,11 @@ class RepairImportService: # Totals come directly from GROUP BY query (no N+1 problem) total_me = to_float(row[24]) # total_me from SUM aggregation total_mn = to_float(row[25]) # total_mn from SUM aggregation + sum_value_usd = to_float(row[27]) + sum_value_mxn = to_float(row[28]) + + total_me = sum_value_usd if sum_value_usd > 0 else total_me + total_mn = sum_value_mxn if sum_value_mxn > 0 else total_mn # Calculate exchange rate and value valor_comercial, tipo_cambio = ExchangeRateCalculator.calculate_for_aggregated( @@ -228,8 +233,8 @@ class RepairImportService: db=db, db_name=filters.database_name, es_subpartida=row[30], # 'P' or 'S' - valor_me=row[20], - valor_mn_direct=row[19], + valor_me=row[21], + valor_mn_direct=row[20], fecha_pago=row[8], fecha_inicio=row[7], clave_ped=row[45], @@ -240,9 +245,9 @@ class RepairImportService: ) # Set peso values based on subpartida flag - if row[30] == 'P': - peso_neto = float(row[21]) if row[21] else 0.0 - peso_bruto = float(row[22]) if row[22] else 0.0 + if row[31] == 'P': + peso_neto = float(row[22]) if row[22] else 0.0 + peso_bruto = float(row[23]) if row[23] else 0.0 else: peso_neto = 0.0 peso_bruto = 0.0 @@ -252,9 +257,9 @@ class RepairImportService: ) simbolo_ex = None - if row[14]: + if row[15]: simbolo_ex = DatabaseHelper.get_part_export_symbol( - db, filters.database_name, row[14], filters.is_shelter + db, filters.database_name, row[15], filters.is_shelter ) pedimento_r1 = DatabaseHelper.get_rectification_pedimento( @@ -275,10 +280,10 @@ class RepairImportService: TipoMovTemDef='IMPRE', EsCambioRegimen='N', Regimen=row[6], - Fecha_Inicio=row[7], - Fecha_Fin=row[7], # Using same valid column or empty - Fecha_Pago=row[8], - Remesa=row[9], + Fecha_Inicio=parse_yyyymmdd_date(row[7]), + Fecha_Fin=parse_yyyymmdd_date(row[8]), + Fecha_Pago=parse_yyyymmdd_date(row[9]), + Remesa=row[10], Proveedor=proveedor_info.get('name'), RFCProveedor=proveedor_info.get('rfc'), ProveedorTaxID=proveedor_info.get('tax_id'), @@ -287,42 +292,42 @@ class RepairImportService: VendidoATaxID=vendido_info.get('tax_id'), AgenteAduanal=agente_info.get('name'), Patente=agente_info.get('license'), - NumParte=row[14], - DescripcionE=StringHelper.clean_text(row[15]), - DescripcionI=StringHelper.clean_text(row[16]), - CantidadIE=float(row[17]) if row[17] else 0.0, - UniMed=row[18], + NumParte=row[15], + DescripcionE=StringHelper.clean_text(row[16]), + DescripcionI=StringHelper.clean_text(row[17]), + CantidadIE=float(row[18]) if row[18] else 0.0, + UniMed=row[19], ValorComercialMN=valor_comercial, TipoCambio=tipo_cambio, PesoNeto=peso_neto, PesoBruto=peso_bruto, - OrdenCompraVenta=row[23], - FraccionArancelaria=row[24], - Preferencia=row[25], - Sector=row[26], - PaisOrigen=row[27], + OrdenCompraVenta=row[24], + FraccionArancelaria=row[25], + Preferencia=row[26], + Sector=row[27], + PaisOrigen=row[28], Aduana=aduana_nombre, - Advalorem=row[30], + Advalorem='', TipoExpo='', PedimentoR1=pedimento_r1, - EDocument=row[32], - NumOperacionVU=row[33], + EDocument=row[33], + NumOperacionVU=row[34], Series=series_info, - Marca=StringHelper.clean_text(row[34]), - Modelo=StringHelper.clean_text(row[35]), - FraccionAmericana=row[36], - ECCN=row[37], + Marca=StringHelper.clean_text(row[35]), + Modelo=StringHelper.clean_text(row[36]), + FraccionAmericana=row[37], + ECCN=row[38], SimboloEx=simbolo_ex, - FechaEmision=row[39], + FechaEmision=parse_yyyymmdd_date(row[40]) if row[40] else None, # C41 - FechaEmision BaseDeDatos=filters.database_name, NumGafUni=num_gaf_uni, - UsuarioCap=row[40], - UsuarioAcr=row[41], - Transportista=row[42], - NumCaja=row[43], - Pedimento18=row[44], - AduanaCru=row[28], - Lote='' # Not in query + UsuarioCap=row[41], + UsuarioAcr=row[42], + Transportista=row[43], + NumCaja=row[44], + Pedimento18=row[45], + AduanaCru=row[29], + Lote=row[54] if len(row) > 54 else '' # Not in query but mapped safely ) movements.append(movement) @@ -347,7 +352,7 @@ class RepairImportService: if filters.range_type.value == "FF": where_conditions.append(f"ih.invoice_date >= TO_DATE('{filters.start_date}', 'YYYYMMDD') AND ih.invoice_date <= TO_DATE('{filters.end_date}', 'YYYYMMDD')") else: - where_conditions.append(f"log.payment_date >= TO_DATE('{filters.start_date}', 'YYYYMMDD') AND log.payment_date <= TO_DATE('{filters.end_date}', 'YYYYMMDD')") + where_conditions.append(f"pd.payment_date >= TO_DATE('{filters.start_date}', 'YYYYMMDD') AND pd.payment_date <= TO_DATE('{filters.end_date}', 'YYYYMMDD')") # Note: Status filter applied at Python level after CASE WHEN in SELECT # because is_updated doesn't directly represent AC/NA status @@ -383,8 +388,7 @@ class RepairImportService: COALESCE(SUM(lf.value_mxn), 0) FROM a76.item_line_financials lf INNER JOIN a76.item_lines il ON il.id = lf.item_line_id - INNER JOIN a76.items itm ON itm.id = il.item_id - WHERE itm.invoice_id = :consecutivo + WHERE il.invoice_id = :consecutivo AND il.is_subitem = false {discharge_clause} """) diff --git a/backend/api/v1/modules/a76/reports/movements/invoices/services/temporary.py b/backend/api/v1/modules/a76/reports/movements/invoices/services/temporary.py index 29d57b0f..40ae067c 100644 --- a/backend/api/v1/modules/a76/reports/movements/invoices/services/temporary.py +++ b/backend/api/v1/modules/a76/reports/movements/invoices/services/temporary.py @@ -97,6 +97,12 @@ class TemporaryImportService: valor_comercial_mn = to_float(row[30]) # valor_comercial_mn from item_line_financials valor_mp_temp_mn = to_float(row[31]) # valor_mp_temp_mn from item_line_financials valor_agre_mn = to_float(row[32]) # valor_agre_mn from item_line_financials + valor_mp_temp_usd = to_float(row[33]) # valor_mp_temp_usd from item_line_financials + sum_value_usd = to_float(row[34]) # sum_value_usd from item_line_financials (line item sum avoids zero-value header bug) + + # Replace zero values with the computed sums + total_me = sum_value_usd if sum_value_usd > 0 else total_me + total_mn = valor_comercial_mn if valor_comercial_mn > 0 else total_mn # Calculate exchange rate and value valor_comercial, tipo_cambio = ExchangeRateCalculator.calculate_for_aggregated( @@ -127,6 +133,24 @@ class TemporaryImportService: num_gaf_uni = DatabaseHelper.get_driver_badge( db, filters.database_name, factura ) + + # Calculate exchange rate for MPTemp explicitly decoupled from Valor Comercial + valor_mp_temp_raw, _ = ExchangeRateCalculator.calculate_for_aggregated( + db=db, + db_name=filters.database_name, + valor_me=to_float(row[33]), # sum_value_temp_usd + valor_mn=to_float(row[31]), # sum_value_temp_mxn + tipo_cambio_db=to_float(row[19]), + fecha_pago=row[8], + fecha_inicio=row[6], + tipo_pedimento=row[4], + currency_type=filters.currency_type.value, + exchange_rate_type=filters.exchange_rate_type.value, + is_shelter=filters.is_shelter, + use_transport_method=False, + met_trans=met_trans + ) + valor_mp_temp = float(valor_mp_temp_raw) # Build movement item movement = MovementItem( @@ -137,8 +161,8 @@ class TemporaryImportService: ClavePed=row[4], # C5 - ClavePed TipoMovTemDef='IMTEM', EsCambioRegimen='N', - ValorMPTemp=valor_mp_temp_mn, - ValorComercialMN=valor_comercial_mn, + ValorMPTemp=valor_mp_temp, + ValorComercialMN=valor_comercial, TipoCambio=tipo_cambio, ValorAgre=valor_agre_mn, TipoExpo='', @@ -212,6 +236,15 @@ class TemporaryImportService: db, filters.database_name, row[17] # C18 - AAduanal ) + # Get provider and client details including RFC and TaxID + provider_info = DatabaseHelper.get_client_info( + db, filters.database_name, row[15], is_supplier=True + ) if row[15] else {} + + client_info = DatabaseHelper.get_client_info( + db, filters.database_name, row[16], is_supplier=False + ) if row[16] else {} + # Get customs section name aduana_nombre = DatabaseHelper.get_aduana_seccion_nombre( db, filters.database_name, row[37] # C38 - Aduana_Cruce @@ -233,13 +266,24 @@ class TemporaryImportService: met_trans=met_trans ) - # ValorComercialMN should always be in MXN - # If currency_type is ME, valor_comercial is in USD, so multiply by tipo_cambio - if filters.currency_type.value == "ME" and tipo_cambio: - valor_comercial_mn = float(valor_comercial) * float(tipo_cambio) - else: - # If currency_type is MN, valor_comercial is already in MXN - valor_comercial_mn = float(valor_comercial) + valor_mp_temp_raw, _ = ExchangeRateCalculator.calculate_exchange_rate_and_value( + db=db, + db_name=filters.database_name, + es_subpartida=row[39], # C40 - EsSubPartida + valor_me=float(row[59]) if row[59] else 0.0, + valor_mn_direct=float(row[58]) if row[58] else 0.0, + fecha_pago=row[12], # C13 - Fecha_Pago + fecha_inicio=row[10], # C11 - Fecha_Inicio + clave_ped=row[57], # C58 - TIPOPEDIMENTOTRANSPORTEE + tipo_cambio_partida=row[49], # C50 - TipoCambio + currency_type=filters.currency_type.value, + exchange_rate_type=filters.exchange_rate_type.value, + met_trans=met_trans + ) + valor_mp_temp_mn = float(valor_mp_temp_raw) + + # Assign the properly converted commercial value directly + valor_comercial_mn = float(valor_comercial) # Set peso values based on subpartida flag if row[39] == 'P': # C40 - EsSubPartida @@ -288,6 +332,8 @@ class TemporaryImportService: return 'P' if val else 'S' # Convert bool to P/S for Advalorem return str(val) + logger.error(f"DEBUG ROW: NumParte(17)='{row[17]}', Sector(30)='{row[30]}', Partida {row[43]}, Original Part ID(46)='{row[46]}'") + # Build detailed movement item movement = MovementItemDetailed( Linea=row[43], # C44 - LineaImpo @@ -299,31 +345,32 @@ class TemporaryImportService: TipoMovTemDef='IMTEM', EsCambioRegimen='N', Regimen=row[9], # C10 - Regimen - Fecha_Inicio=none_if_empty(row[10]), # C11 - Fecha_Inicio - Fecha_Fin=none_if_empty(row[11]), # C12 - Fecha_Fin - Fecha_Pago=none_if_empty(row[12]), # C13 - Fecha_Pago + Fecha_Inicio=parse_yyyymmdd_date(row[10]), # C11 - Fecha_Inicio + Fecha_Fin=parse_yyyymmdd_date(row[11]), # C12 - Fecha_Fin + Fecha_Pago=parse_yyyymmdd_date(row[12]), # C13 - Fecha_Pago Remesa=to_str(row[13]), # C14 - Remesa Proveedor=row[7], # C8 - Provider name (from JOIN) - RFCProveedor=None, # RFC not in detailed query - ProveedorTaxID=None, # Tax ID not in detailed query + RFCProveedor=provider_info.get('rfc'), + ProveedorTaxID=provider_info.get('tax_id'), VendidoA=row[8], # C9 - Client name (from JOIN) - VendidoARFC=None, # RFC not in detailed query - VendidoATaxID=None, # Tax ID not in detailed query + VendidoARFC=client_info.get('rfc'), + VendidoATaxID=client_info.get('tax_id'), AgenteAduanal=agente_info.get('name'), Patente=agente_info.get('license'), - NumParte=row[19], # C20 - Clase (NumParte) + NumParte=row[48], # C49 - Part Number (from JOIN) DescripcionE=StringHelper.clean_text(row[20]), # C21 DescripcionI=StringHelper.clean_text(row[21]), # C22 CantidadIE=float(row[22]) if row[22] else 0.0, # C23 UniMed=row[23], # C24 ValorComercialMN=valor_comercial_mn, + ValorMPTemp=valor_mp_temp_mn, TipoCambio=tipo_cambio, PesoNeto=peso_neto, PesoBruto=peso_bruto, OrdenCompraVenta=row[30], # C31 - OrdenCompra FraccionArancelaria=row[31], # C32 - Fraccion Preferencia=row[32], # C33 - TipoFraccion - Sector=row[34], # C35 - Sector + Sector=row[34], # C35 - Sector (from COALESCE) PaisOrigen=row[36], # C37 - PaisOrigen Aduana=aduana_nombre, Advalorem=to_str(row[39]), # C40 - EsSubPartida (convert bool to str) @@ -337,13 +384,13 @@ class TemporaryImportService: FraccionAmericana=row[46], # C47 - FraccionAme ECCN=row[47], # C48 - ECCN SimboloEx=simbolo_ex, - FechaEmision=parse_yyyymmdd_date(row[50]), # C51 - FechaEmision (convert to datetime) + FechaEmision=parse_yyyymmdd_date(row[50]), # C51 - FechaEmision BaseDeDatos=filters.database_name, NumGafUni=num_gaf_uni, UsuarioCap=row[51], # C52 - UsuarioCap UsuarioAcr=row[52], # C53 - UsuarioAct - Transportista=row[53], # C54 - Transportista - NumCaja=row[54], # C55 - Transporte + NumTrasporte + Transportista=row[53], # C54 - Carrier ID + NumCaja=row[54], # C55 - Transport num Pedimento18=row[55], # C56 - Pedimento18 AduanaCru=row[37], # C38 - Aduana_Cruce Lote=row[56] # C57 - LOTE @@ -371,7 +418,7 @@ class TemporaryImportService: if filters.range_type.value == "FF": where_conditions.append(f"ih.invoice_date >= TO_DATE('{filters.start_date}', 'YYYYMMDD') AND ih.invoice_date <= TO_DATE('{filters.end_date}', 'YYYYMMDD')") else: - where_conditions.append(f"log.payment_date >= TO_DATE('{filters.start_date}', 'YYYYMMDD') AND log.payment_date <= TO_DATE('{filters.end_date}', 'YYYYMMDD')") + where_conditions.append(f"pd.payment_date >= TO_DATE('{filters.start_date}', 'YYYYMMDD') AND pd.payment_date <= TO_DATE('{filters.end_date}', 'YYYYMMDD')") # Note: Status filter applied at Python level after CASE WHEN in SELECT # because is_updated doesn't directly represent AC/NA status @@ -401,8 +448,7 @@ class TemporaryImportService: COALESCE(SUM(lf.value_mxn), 0) FROM a76.item_line_financials lf JOIN a76.item_lines il ON il.id = lf.item_line_id - JOIN a76.items i ON i.id = il.item_id - WHERE i.invoice_id = :consecutivo + WHERE il.invoice_id = :consecutivo AND COALESCE(il.is_subpartida, false) = false """) diff --git a/frontend/src/lib/api/dashboard/a24/fa_classes.ts b/frontend/src/lib/api/dashboard/a24/fa_classes.ts index 91359f52..e65f4625 100644 --- a/frontend/src/lib/api/dashboard/a24/fa_classes.ts +++ b/frontend/src/lib/api/dashboard/a24/fa_classes.ts @@ -98,7 +98,7 @@ export const faClassesApi = { * Actualizar una clase de activo fijo existente */ update: (id: number, data: FAClassUpdate, company_id: number): Promise> => { - return api.put(`/v1/a24/fa/classes/${id}?company_id=${company_id}`, data); + return api.put(`/v1/a24/fa/classes/${id}/?company_id=${company_id}`, data); }, /** diff --git a/frontend/src/lib/api/dashboard/a76/classes.ts b/frontend/src/lib/api/dashboard/a76/classes.ts index 236c2cdb..de2a8ca5 100644 --- a/frontend/src/lib/api/dashboard/a76/classes.ts +++ b/frontend/src/lib/api/dashboard/a76/classes.ts @@ -17,6 +17,7 @@ export interface A76Class { sub_key: string; physical_review: number; iva_exempt_fraction: string; + eccn_code?: string | null; is_active?: boolean; // Agregado para el switch del formulario created_at: string; updated_at: string; @@ -35,6 +36,7 @@ export interface A76ClassCreate { sub_key?: string | null; physical_review?: number | null; iva_exempt_fraction?: string | null; + eccn_code?: string | null; is_active?: boolean; } diff --git a/frontend/src/lib/components/dashboard/goods/classes/forms/FixedAssetClassForm.svelte b/frontend/src/lib/components/dashboard/goods/classes/forms/FixedAssetClassForm.svelte index e0977939..fb406f32 100644 --- a/frontend/src/lib/components/dashboard/goods/classes/forms/FixedAssetClassForm.svelte +++ b/frontend/src/lib/components/dashboard/goods/classes/forms/FixedAssetClassForm.svelte @@ -732,18 +732,14 @@
- -
- - -
+ +
diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/item-configuration.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/item-configuration.svelte index 8bc0f930..af2950b8 100644 --- a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/item-configuration.svelte +++ b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/item-configuration.svelte @@ -5,14 +5,14 @@ import { Button } from '$lib/components/ui/button'; import { Folder } from 'lucide-svelte'; import PartNumberDialog from './part-number-dialog.svelte'; - import type { Item, LineDescriptions } from '$lib/api/dashboard/a76/items'; + import type { Item, LineDescriptions } from '$lib/api/dashboard/a76/items'; - let { + let { lineItem = $bindable(), - descriptions = $bindable() - }: { + descriptions = $bindable() + }: { lineItem: Partial; - descriptions: LineDescriptions; + descriptions: LineDescriptions; } = $props(); let showPartDialog = $state(false); @@ -22,30 +22,30 @@ lineItem.fa_data = {}; } - // Helper to map boolean to string for RadioGroup - let isSubPartidaValue = $derived(lineItem.fa_data?.is_subitem ? 'subpartida' : 'partida'); - function setIsSubPartida(val: string) { - if (!lineItem.fa_data) lineItem.fa_data = {}; - lineItem.fa_data.is_subitem = val === 'subpartida'; - } + // Helper to map boolean to string for RadioGroup + let isSubPartidaValue = $derived(lineItem.fa_data?.is_subitem ? 'subpartida' : 'partida'); + function setIsSubPartida(val: string) { + if (!lineItem.fa_data) lineItem.fa_data = {}; + lineItem.fa_data.is_subitem = val === 'subpartida'; + } - let containsSubPartidasValue = $derived(lineItem.fa_data?.contains_subitems ? 'si' : 'no'); - function setContinueSubPartidas(val: string) { - if (!lineItem.fa_data) lineItem.fa_data = {}; - lineItem.fa_data.contains_subitems = val === 'si'; - } + let containsSubPartidasValue = $derived(lineItem.fa_data?.contains_subitems ? 'si' : 'no'); + function setContinueSubPartidas(val: string) { + if (!lineItem.fa_data) lineItem.fa_data = {}; + lineItem.fa_data.contains_subitems = val === 'si'; + } - // Helper for subitem_number binding - let subitemNumber = $derived.by(() => lineItem.fa_data?.subitem_number ?? 0); - function setSubitemNumber(val: number) { - if (!lineItem.fa_data) lineItem.fa_data = {}; - lineItem.fa_data.subitem_number = val; - } + // Helper for subitem_number binding + let subitemNumber = $derived.by(() => lineItem.fa_data?.subitem_number ?? 0); + function setSubitemNumber(val: number) { + if (!lineItem.fa_data) lineItem.fa_data = {}; + lineItem.fa_data.subitem_number = val; + } function handlePartSelect(part: any) { lineItem.part_number = part.id; // Store part number for display - (lineItem as any).part_number = part.part_number; + (lineItem as any).part_number_display = part.part_number; (lineItem as any).part_description_es = part.description_spanish; (lineItem as any).part_description_en = part.description_english; } @@ -53,15 +53,12 @@ -
+
-
- Is - +
+ Is +
@@ -73,12 +70,15 @@
{#if isSubPartidaValue === 'partida'} -
- Contains Sub-Items - + Contains Sub-Items + + class="flex gap-3" + >
@@ -90,31 +90,33 @@
{:else if isSubPartidaValue === 'subpartida'} -
- Main Item Number - + Main Item Number + setSubitemNumber(e.currentTarget.valueAsNumber || 0)} - class="h-7 text-xs" + class="h-7 text-xs" placeholder="Enter main item number" /> -
- {/if} +
+ {/if}
-
+
- (showPartDialog = true)} /> @@ -124,29 +126,33 @@ class="h-7 w-7 shrink-0" onclick={() => (showPartDialog = true)} > - +
{#if (lineItem as any).part_description_es} -

{(lineItem as any).part_description_es}

+

+ {(lineItem as any).part_description_es} +

{/if}
- -
-
diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/inv/item-sheet-inv.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/inv/item-sheet-inv.svelte index a4a10bb3..13b20bf5 100644 --- a/frontend/src/lib/components/dashboard/invoices/edit/items/inv/item-sheet-inv.svelte +++ b/frontend/src/lib/components/dashboard/invoices/edit/items/inv/item-sheet-inv.svelte @@ -2,14 +2,16 @@ import * as Sheet from '$lib/components/ui/sheet'; import * as Tabs from '$lib/components/ui/tabs'; import { Input } from '$lib/components/ui/input'; + import { Textarea } from '$lib/components/ui/textarea'; import { Label } from '$lib/components/ui/label'; import { Button } from '$lib/components/ui/button'; import { Badge } from '$lib/components/ui/badge'; - import { Loader2, FileText } from 'lucide-svelte'; + import { Loader2, FileText, Folder } from 'lucide-svelte'; import type { Invoice } from '$lib/api/dashboard/a76/invoices'; import type { Item } from '$lib/api/dashboard/a76/items'; import { useShortcuts } from '$lib/hooks/use-shortcuts'; import { obtenerAtajosPestanasItemInv } from '$lib/config/shortcuts/dashboard/invoices/item/inventory'; + import PartNumberDialog from '../fa/part-number-dialog.svelte'; let { open = $bindable(), @@ -34,6 +36,7 @@ } = $props(); let activeTab = $state('general'); + let showPartDialog = $state(false); const tabMapping: Record = { tab1: 'general', @@ -42,6 +45,19 @@ tab4: 'otros' }; + function handlePartSelect(part: any) { + editingItem.part_number = part.id; + // Store part number for display + (editingItem as any).part_number_display = part.part_number; + if (editingItem.description) { + editingItem.description.description_spanish = part.description_spanish; + editingItem.description.description_english = part.description_english; + } + if (editingItem.customs) { + editingItem.customs.fraction = part.fraction; + } + } + useShortcuts( 'Invoice Item Form (Inventory)', obtenerAtajosPestanasItemInv({ @@ -60,18 +76,16 @@ // Initialize missing nested objects if they don't exist $effect(() => { if (open && editingItem) { - if (editingItem && !editingItem.quantity) - editingItem.quantity = {} as any; - if (editingItem && !editingItem.financial) - editingItem.financial = {} as any; - if (editingItem && !editingItem.customs) - editingItem.customs = {} as any; - if (editingItem && !editingItem.description) - editingItem.description = {} as any; + if (editingItem && !editingItem.quantity) editingItem.quantity = {} as any; + if (editingItem && !editingItem.financial) editingItem.financial = {} as any; + if (editingItem && !editingItem.customs) editingItem.customs = {} as any; + if (editingItem && !editingItem.description) editingItem.description = {} as any; } }); + +
{#if line} - +
+ (showPartDialog = true)} + /> + +
{/if}
@@ -227,20 +249,20 @@
{#if line?.description} - {/if}
{#if line?.description} - {/if}
@@ -363,7 +385,7 @@ {/if}
@@ -377,7 +399,7 @@ id="imported_quantity" type="number" placeholder="0" - bind:value={(line.quantity as any).quantity_imported} + bind:value={(line.quantity as any).quantity_imported} /> {/if}
diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/items-tab-form.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/items-tab-form.svelte index a8e24e06..372198ff 100644 --- a/frontend/src/lib/components/dashboard/invoices/edit/items/items-tab-form.svelte +++ b/frontend/src/lib/components/dashboard/invoices/edit/items/items-tab-form.svelte @@ -87,6 +87,7 @@ item?.description?.description_english || '', unit_of_measure_code: item?.quantity?.unit_of_measure || item?.unit_of_measure, + part_number_display: (item as any).part_number_display || item?.part_number, fa_data: item?.fa_data || {}, warehouse: item?.warehouse, full_item: item @@ -261,11 +262,11 @@ payment_method: undefined, igi_amount: undefined, is_military_mcia: false, - wildcard_field: undefined, + wildcard_field: undefined, reference_number: '', order: invoice?.purchase_order || '', warehouse: '', - location: '', + location: '', // Nested relations financial: { unit_cost_usd: undefined, @@ -314,7 +315,7 @@ }, reference: { serie_id: undefined - } + } }; } @@ -422,8 +423,7 @@ quantity: Number(draft.quantity) || 0 }, financial: { - unit_cost_usd: - draft.unit_cost_usd !== null ? Number(draft.unit_cost_usd) || 0 : undefined + unit_cost_usd: draft.unit_cost_usd !== null ? Number(draft.unit_cost_usd) || 0 : undefined } }); } @@ -574,15 +574,15 @@ } // Load part number data - if (item.part_number) { + if (item.part_number_id) { try { const response = await fetch( - `/api-sveltekit/parts/${item.part_number}?company_id=${activeCompanyId}`, + `/api-sveltekit/parts/${item.part_number_id}?company_id=${activeCompanyId}`, { method: 'GET', headers: { 'Content-Type': 'application/json' } } ); if (response.ok) { const partData = await response.json(); - (item as any).part_number = partData.part_number; + (item as any).part_number_display = partData.part_number; (item as any).part_description_es = partData.description_spanish; (item as any).part_description_en = partData.description_english; } @@ -661,7 +661,8 @@ if (Array.isArray(packages)) { const pkg = packages.find((p: any) => p.id === packageId); if (pkg) { - (item.quantity as any).package_description = pkg.description_es || pkg.description_en || pkg.key; + (item.quantity as any).package_description = + pkg.description_es || pkg.description_en || pkg.key; (item.quantity as any).package_key = pkg.key; (item.quantity as any).package_weight_unit = pkg.weight_unit || 0; } @@ -990,15 +991,15 @@
-
-
+
+

Items de la Factura

Carga partidas, crea o aplica plantillas sin salir de esta vista.

-
+
@@ -1037,7 +1038,7 @@ Preferencia Contiene Subpartida Partida Principal - Acciones + Acciones @@ -1066,7 +1067,7 @@ {item.is_subitem ? 'S' : 'P'} {item.quantity?.quantity || '0'} {item.class_code || '-'} - {item.part_number || '-'} + {item.part_number_display || '-'} {item.is_subitem ? 'S' : 'P'} {item.quantity?.quantity || '0'} {item.class_code || '-'} - {item.part_number || '-'} + {item.part_number_display || '-'} {item.fa_data?.search_line || '-'} {item.is_subitem ? 'S' : 'P'} {item.class_code || '-'} - {item.part_number || '-'} + {item.part_number_display || '-'}
@@ -1249,10 +1250,10 @@ }} > -
+
Usar plantilla @@ -1267,7 +1268,7 @@ disabled={isLoadingPresets} class="h-8 text-muted-foreground" > - + Actualizar
-
+
-
+
@@ -1303,47 +1304,47 @@
{#if isLoadingPresets}
- + Cargando...
{:else if filteredPresets.length === 0}
- +

No se encontraron plantillas

{:else} {#each filteredPresets as preset} @@ -1496,7 +1495,11 @@
- +
@@ -1510,18 +1513,18 @@
-
+
{builderItems.length} items/líneas
-
-
- +
+ Items de la plantilla
@@ -1531,7 +1534,7 @@ # Descripción Cant. - Acciones + Acciones @@ -1539,7 +1542,7 @@ Usa el botón "Agregar Item/Línea" para definir el contenido de la plantilla. @@ -1552,7 +1555,7 @@
- + {item?.[0]?.description?.description_spanish || 'Sin descripción'} {item?.[0]?.quantity?.quantity || 0} - +
@@ -1597,14 +1600,14 @@ diff --git a/frontend/src/lib/components/dashboard/invoices/edit/save-invoice.ts b/frontend/src/lib/components/dashboard/invoices/edit/save-invoice.ts index 3df967a2..141a1fbd 100644 --- a/frontend/src/lib/components/dashboard/invoices/edit/save-invoice.ts +++ b/frontend/src/lib/components/dashboard/invoices/edit/save-invoice.ts @@ -114,7 +114,7 @@ function buildInvoicePayload(formData: FormDataSet): CreateInvoiceData | UpdateI invoice_type: InvoiceTopFieldsFormData?.invoice_type || undefined, document_type: generalFormData?.document_type || undefined, invoice_number: InvoiceTopFieldsFormData?.invoice_number || undefined, - purchase_order: InvoiceTopFieldsFormData?.purchase_order || undefined, + purchase_order: InvoiceTopFieldsFormData?.purchase_order || continuationFormData?.purchase_order || undefined, invoice_date: InvoiceTopFieldsFormData?.invoice_date || undefined, emission_date: InvoiceTopFieldsFormData?.emission_date || undefined, proforma_number: observationFormData?.proforma_number || undefined, diff --git a/frontend/src/routes/dashboard/goods/fixed-asset-classes/+page.svelte b/frontend/src/routes/dashboard/goods/fixed-asset-classes/+page.svelte index ebde6753..5a0af337 100644 --- a/frontend/src/routes/dashboard/goods/fixed-asset-classes/+page.svelte +++ b/frontend/src/routes/dashboard/goods/fixed-asset-classes/+page.svelte @@ -16,7 +16,11 @@ fa_class_id?: number; depreciation_rate?: number | null; fda_code?: string | null; + eccn_code?: string | null; class_enabled?: boolean | null; + // Virtual fields for form compatibility + annual_depreciation_rate?: number | string | null; + fda_key?: string | null; } // Estado de la lista de clases @@ -42,6 +46,9 @@ fraction: '', us_fraction: '', unit_measure_trade: '', + depreciation_rate: null as number | null, + fda_code: '', + eccn_code: '', bom: '' }); @@ -117,227 +124,13 @@ fraction: cls.fraction || '', us_fraction: cls.us_fraction || '', unit_measure_trade: '', + depreciation_rate: (cls as FixedAssetClassExtended).depreciation_rate || null, + fda_code: (cls as FixedAssetClassExtended).fda_code || '', + eccn_code: (cls as FixedAssetClassExtended).eccn_code || '', bom: '' }; } - async function saveFixedAssetClass(formData: any) { - const companyId = companyStore.activeCompany?.id; - - // CAMBIO: Usar $state.snapshot para obtener una copia real, no reactiva - const data = $state.snapshot(formData); - - if (!companyId) { - toast.error('No hay empresa seleccionada'); - throw new Error('No hay empresa seleccionada'); - } - - // Validar campos obligatorios - const missingFields: string[] = []; - - if (!data.class_code?.trim()) { - missingFields.push('Código de clase'); - } - if (!data.description_es?.trim()) { - missingFields.push('Descripción en español'); - } - if (!data.material_key?.trim()) { - missingFields.push('Tipo de activo fijo'); - } - if (!data.unit_of_measure?.trim()) { - missingFields.push('Unidad de medida comercial'); - } - if (!data.fraction?.trim()) { - missingFields.push('Fracción arancelaria'); - } - - if (missingFields.length > 0) { - const fieldsList = missingFields.join(', '); - validationError = `Debe completar los siguientes campos obligatorios: ${fieldsList}`; - toast.error(validationError, { - duration: 8000 - }); - throw new Error(`Campos obligatorios faltantes: ${fieldsList}`); - } - - // Limpiar error de validación si todo está bien - validationError = ''; - - try { - // Usar el endpoint combinado /fa que crea ambos registros en una transacción - const payload = { - class_code: data.class_code.trim(), - description_es: data.description_es.trim(), - description_en: data.description_en?.trim() || '', - material_key: data.material_key.trim(), - unit_of_measure: data.unit_of_measure.trim(), - fraction: data.fraction.trim(), - us_fraction: data.us_fraction?.trim() || '', - sub_key: data.sub_key || '', - physical_review: data.physical_review ? 1 : 0, - iva_exempt_fraction: data.iva_exempt_fraction || '', - // FA-specific fields - import_tariff_code: data.import_tariff_code || null, - import_tariff_type: data.import_tariff_type || null, - export_tariff_code: data.export_tariff_code || null, - export_tariff_type: data.export_tariff_type || null, - depreciation_rate: data.annual_depreciation_rate || null, - fda_code: data.fda_key || null, - eccn_code: data.eccn_code || null, - class_enabled: true - }; - - const response = await classesApi.createFA(payload, companyId); - - if (response.error) { - console.error('Server error:', response.error); - - // Manejar diferentes formatos de error - let errorMessage = response.error; - let isDuplicateError = false; - - // Detectar si es un error de código duplicado - if (errorMessage.includes('código') && errorMessage.includes('ya está en uso')) { - isDuplicateError = true; - } - - // Mensaje más específico para errores de duplicado - if (isDuplicateError) { - validationError = `⚠️ ${errorMessage}\n\nPor favor, cambie el código de clase a uno diferente.`; - } else { - validationError = `⚠️ ${errorMessage}`; - } - - toast.error(errorMessage, { duration: 8000 }); - throw new Error(errorMessage); - } - - validationError = ''; - toast.success('✅ Clase de activo fijo creada correctamente'); - return response.data; - } catch (error: any) { - console.error('Error saving fixed asset class:', error); - // El toast ya se mostró arriba, solo re-lanzar el error - throw error; - } - } - - async function updateFixedAssetClass(formData: any) { - const companyId = companyStore.activeCompany?.id; - - // CAMBIO 1: Usar $state.snapshot para obtener una copia real, no reactiva - // Esto garantiza que aunque el hijo borre el formulario, 'data' mantenga los valores - const data = $state.snapshot(formData); - - if (!companyId || !selectedClass) { - toast.error('No hay empresa o clase seleccionada'); - return; - } - - // CAMBIO 2: Validar sobre 'data' (la copia muerta) - const missingFields: string[] = []; - if (!data.class_code?.trim()) missingFields.push('Código de clase'); - if (!data.description_es?.trim()) missingFields.push('Descripción en español'); - if (!data.material_key?.trim()) missingFields.push('Tipo de activo fijo'); - if (!data.unit_of_measure?.trim()) missingFields.push('Unidad de medida comercial'); - if (!data.fraction?.trim()) missingFields.push('Fracción arancelaria'); - - if (missingFields.length > 0) { - const errorMsg = `Campos obligatorios faltantes: ${missingFields.join(', ')}`; - validationError = `⚠️ ${errorMsg}`; - toast.error(errorMsg); - // Lanzamos el error para que el 'onSave' del Dialog no cierre la ventana - throw new Error(errorMsg); - } - - validationError = ''; - - try { - // CAMBIO 3: Usar siempre 'data' para los payloads - const a76Response = await classesApi.update( - selectedClass.id, - { - class_code: data.class_code.trim(), - description_es: data.description_es.trim(), - description_en: data.description_en?.trim() || '', - material_key: data.material_key.trim(), - unit_of_measure: data.unit_of_measure.trim(), - fraction: data.fraction.trim(), - us_fraction: data.us_fraction || '', - physical_review: data.physical_review ? 1 : 0, - iva_exempt_fraction: data.iva_exempt_fraction || '' - }, - companyId - ); - - if (selectedClass.fa_class_id) { - await faClassesApi.update( - selectedClass.fa_class_id, - { - depreciation_rate: data.annual_depreciation_rate || null, - fda_code: data.fda_key || null - }, - companyId - ); - } else { - await faClassesApi.create( - { - class_id: selectedClass.id, - depreciation_rate: data.annual_depreciation_rate || null, - fda_code: data.fda_key || null, - class_enabled: true - }, - companyId - ); - } - - toast.success('Clase actualizada correctamente'); - return { a76: a76Response.data }; - } catch (error: any) { - console.error('Error updating fixed asset class:', error); - console.error('Error response:', error?.response); - console.error('Error response data:', error?.response?.data); - console.error('Error response detail:', error?.response?.data?.detail); - console.error('Error type:', typeof error?.response?.data?.detail); - - let errorMessage = 'Error al actualizar la clase'; - let isDuplicateError = false; - - // Extract error message from response - if (error?.response?.data?.detail) { - if (Array.isArray(error.response.data.detail)) { - errorMessage = error.response.data.detail - .map((e: any) => `${e.loc ? e.loc.join(' → ') : ''}: ${e.msg || e}`) - .join(', '); - } else if (typeof error.response.data.detail === 'string') { - errorMessage = error.response.data.detail; - // Detectar si es un error de código duplicado - if (errorMessage.includes('código') && errorMessage.includes('ya está en uso')) { - isDuplicateError = true; - } - } else { - errorMessage = JSON.stringify(error.response.data.detail); - } - } else if (error?.message) { - errorMessage = error.message; - } - - console.error('Final error message:', errorMessage); - console.error('Is duplicate error:', isDuplicateError); - - // Mensaje más específico para errores de duplicado - if (isDuplicateError) { - validationError = `⚠️ ${errorMessage}\n\nPor favor, cambie el código de clase a uno diferente.`; - } else { - validationError = `⚠️ ${errorMessage}`; - } - - toast.error(errorMessage, { duration: 8000 }); - - console.error('Toast shown, about to throw error'); - throw error; - } - } function handleNew() { selectedClass = null; formData = { @@ -349,6 +142,9 @@ fraction: '', us_fraction: '', unit_measure_trade: '', + depreciation_rate: null as number | null, + fda_code: '', + eccn_code: '', bom: '' }; } @@ -397,6 +193,9 @@ fraction: '', us_fraction: '', unit_measure_trade: '', + depreciation_rate: null as number | null, + fda_code: '', + eccn_code: '', bom: '' }; } catch (error) { @@ -629,6 +428,18 @@ {formData.fraction || '0000.00.00'}

+ + +
+ +

+ {formData.eccn_code || '---'} +

+
@@ -752,6 +563,48 @@ companyId ); + // También actualizar la extensión FA + if (selectedClass.fa_class_id) { + await faClassesApi.update( + selectedClass.fa_class_id, + { + depreciation_rate: + cleanData.annual_depreciation_rate !== undefined && + cleanData.annual_depreciation_rate !== null && + cleanData.annual_depreciation_rate !== '' + ? Number(cleanData.annual_depreciation_rate) + : cleanData.depreciation_rate !== undefined && + cleanData.depreciation_rate !== null && + cleanData.depreciation_rate != null + ? Number(cleanData.depreciation_rate) + : null, + fda_code: (cleanData.fda_key ?? cleanData.fda_code) || null, + eccn_code: cleanData.eccn_code || null + }, + companyId + ); + } else { + await faClassesApi.create( + { + class_id: selectedClass.id, + depreciation_rate: + cleanData.annual_depreciation_rate !== undefined && + cleanData.annual_depreciation_rate !== null && + cleanData.annual_depreciation_rate !== '' + ? Number(cleanData.annual_depreciation_rate) + : cleanData.depreciation_rate !== undefined && + cleanData.depreciation_rate !== null && + cleanData.depreciation_rate != null + ? Number(cleanData.depreciation_rate) + : null, + fda_code: (cleanData.fda_key ?? cleanData.fda_code) || null, + eccn_code: cleanData.eccn_code || null, + class_enabled: true + }, + companyId + ); + } + // ¡IMPORTANTE! fetchApi NO lanza excepciones, retorna { error, status } if (response.error) { console.error('❌ Error en respuesta de actualización:', response); @@ -770,8 +623,18 @@ sub_key: cleanData.sub_key || '', physical_review: cleanData.physical_review ? 1 : 0, iva_exempt_fraction: cleanData.iva_exempt_fraction || '', - depreciation_rate: cleanData.depreciation_rate || null, - fda_code: cleanData.fda_code || null, + depreciation_rate: + cleanData.annual_depreciation_rate !== undefined && + cleanData.annual_depreciation_rate !== null && + cleanData.annual_depreciation_rate !== '' + ? Number(cleanData.annual_depreciation_rate) + : cleanData.depreciation_rate !== undefined && + cleanData.depreciation_rate !== null && + cleanData.depreciation_rate != null + ? Number(cleanData.depreciation_rate) + : null, + fda_code: (cleanData.fda_key ?? cleanData.fda_code) || null, + eccn_code: cleanData.eccn_code || null, class_enabled: true }; diff --git a/frontend/src/routes/dashboard/reports/invoices/+page.svelte b/frontend/src/routes/dashboard/reports/invoices/+page.svelte index 07b043a8..2776ce68 100644 --- a/frontend/src/routes/dashboard/reports/invoices/+page.svelte +++ b/frontend/src/routes/dashboard/reports/invoices/+page.svelte @@ -457,19 +457,22 @@ document.body.removeChild(link); window.URL.revokeObjectURL(url); - toast.success('Reporte generado y descargado. También se ha enviado por correo.', { - id: toastId - }); + toast.success( + `Reporte generado y descargado.${config.sendEmail ? ' También se ha enviado por correo.' : ''}`, + { + id: toastId + } + ); } catch (downloadErr) { console.error('Error downloading file:', downloadErr); toast.success( - 'Reporte generado correctamente. Se ha enviado un correo con los resultados.', + `Reporte generado correctamente.${config.sendEmail ? ' Se ha enviado un correo con los resultados.' : ''}`, { id: toastId } ); } } else { toast.success( - 'Reporte generado correctamente. Se ha enviado un correo con los resultados.', + `Reporte generado correctamente.${config.sendEmail ? ' Se ha enviado un correo con los resultados.' : ''}`, { id: toastId } ); }