feature/migraciones-id-transportes

This commit is contained in:
2026-03-25 10:38:24 -06:00
parent d35f02563a
commit b477233245
24 changed files with 649 additions and 63 deletions

View File

@@ -0,0 +1,266 @@
"""Add surrogate int IDs for transport catalogs and invoice logistics.
Single consolidated migration for:
- transporter_id, trailer_id, vehicle_id, driver_id
- carrier_int_id, transport_int_id, trailer_int_id in invoice_logistics
- backfill mappings from existing string keys/codes
- FK constraints for the new invoice_logistics int references
"""
from __future__ import annotations
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
revision: str = "ca7d3c4e8b2a"
down_revision: Union[str, Sequence[str], None] = "c1a2b3d4e5f6"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# transporter
op.add_column("transporter", sa.Column("transporter_id", sa.BigInteger(), nullable=True), schema="a76")
op.execute(
"""
WITH s AS (
SELECT transporter_key, ROW_NUMBER() OVER (ORDER BY transporter_key) AS new_id
FROM a76.transporter
)
UPDATE a76.transporter t
SET transporter_id = s.new_id
FROM s
WHERE t.transporter_key = s.transporter_key
"""
)
op.execute("CREATE SEQUENCE IF NOT EXISTS a76.transporter_transporter_id_seq")
op.execute(
"""
SELECT setval(
'a76.transporter_transporter_id_seq',
COALESCE((SELECT MAX(transporter_id) FROM a76.transporter), 0) + 1,
false
)
"""
)
op.execute(
"""
ALTER TABLE a76.transporter
ALTER COLUMN transporter_id
SET DEFAULT nextval('a76.transporter_transporter_id_seq')
"""
)
op.execute("ALTER TABLE a76.transporter ALTER COLUMN transporter_id SET NOT NULL")
op.create_unique_constraint("uq_a76_transporter_transporter_id", "transporter", ["transporter_id"], schema="a76")
# trailer
op.add_column("trailer", sa.Column("trailer_id", sa.BigInteger(), nullable=True), schema="a76")
op.execute(
"""
WITH s AS (
SELECT trailer_number, ROW_NUMBER() OVER (ORDER BY trailer_number) AS new_id
FROM a76.trailer
)
UPDATE a76.trailer t
SET trailer_id = s.new_id
FROM s
WHERE t.trailer_number = s.trailer_number
"""
)
op.execute("CREATE SEQUENCE IF NOT EXISTS a76.trailer_trailer_id_seq")
op.execute(
"""
SELECT setval(
'a76.trailer_trailer_id_seq',
COALESCE((SELECT MAX(trailer_id) FROM a76.trailer), 0) + 1,
false
)
"""
)
op.execute(
"""
ALTER TABLE a76.trailer
ALTER COLUMN trailer_id
SET DEFAULT nextval('a76.trailer_trailer_id_seq')
"""
)
op.execute("ALTER TABLE a76.trailer ALTER COLUMN trailer_id SET NOT NULL")
op.create_unique_constraint("uq_a76_trailer_trailer_id", "trailer", ["trailer_id"], schema="a76")
# vehicle
op.add_column("vehicle", sa.Column("vehicle_id", sa.BigInteger(), nullable=True), schema="a76")
op.execute(
"""
WITH s AS (
SELECT vehicle_key, ROW_NUMBER() OVER (ORDER BY vehicle_key) AS new_id
FROM a76.vehicle
)
UPDATE a76.vehicle v
SET vehicle_id = s.new_id
FROM s
WHERE v.vehicle_key = s.vehicle_key
"""
)
op.execute("CREATE SEQUENCE IF NOT EXISTS a76.vehicle_vehicle_id_seq")
op.execute(
"""
SELECT setval(
'a76.vehicle_vehicle_id_seq',
COALESCE((SELECT MAX(vehicle_id) FROM a76.vehicle), 0) + 1,
false
)
"""
)
op.execute(
"""
ALTER TABLE a76.vehicle
ALTER COLUMN vehicle_id
SET DEFAULT nextval('a76.vehicle_vehicle_id_seq')
"""
)
op.execute("ALTER TABLE a76.vehicle ALTER COLUMN vehicle_id SET NOT NULL")
op.create_unique_constraint("uq_a76_vehicle_vehicle_id", "vehicle", ["vehicle_id"], schema="a76")
# driver
op.add_column("driver", sa.Column("driver_id", sa.BigInteger(), nullable=True), schema="a76")
op.execute(
"""
WITH s AS (
SELECT transporter_key, line, ROW_NUMBER() OVER (ORDER BY transporter_key, line) AS new_id
FROM a76.driver
)
UPDATE a76.driver d
SET driver_id = s.new_id
FROM s
WHERE d.transporter_key = s.transporter_key
AND d.line = s.line
"""
)
op.execute("CREATE SEQUENCE IF NOT EXISTS a76.driver_driver_id_seq")
op.execute(
"""
SELECT setval(
'a76.driver_driver_id_seq',
COALESCE((SELECT MAX(driver_id) FROM a76.driver), 0) + 1,
false
)
"""
)
op.execute(
"""
ALTER TABLE a76.driver
ALTER COLUMN driver_id
SET DEFAULT nextval('a76.driver_driver_id_seq')
"""
)
op.execute("ALTER TABLE a76.driver ALTER COLUMN driver_id SET NOT NULL")
op.create_unique_constraint("uq_a76_driver_driver_id", "driver", ["driver_id"], schema="a76")
# invoice_logistics int refs
op.add_column("invoice_logistics", sa.Column("carrier_int_id", sa.BigInteger(), nullable=True), schema="a76")
op.add_column("invoice_logistics", sa.Column("transport_int_id", sa.BigInteger(), nullable=True), schema="a76")
op.add_column("invoice_logistics", sa.Column("trailer_int_id", sa.BigInteger(), nullable=True), schema="a76")
# ensure referenced codes exist
op.execute(
"""
INSERT INTO a76.transporter (transporter_key, tenant_id, company_id)
SELECT DISTINCT ON (il.carrier_id) il.carrier_id, il.tenant_id, il.company_id
FROM a76.invoice_logistics il
WHERE il.carrier_id IS NOT NULL
AND NOT EXISTS (
SELECT 1 FROM a76.transporter t WHERE t.transporter_key = il.carrier_id
)
"""
)
op.execute(
"""
INSERT INTO a76.vehicle (vehicle_key, tenant_id, company_id)
SELECT DISTINCT ON (il.transport_id) il.transport_id, il.tenant_id, il.company_id
FROM a76.invoice_logistics il
WHERE il.transport_id IS NOT NULL
AND NOT EXISTS (
SELECT 1 FROM a76.vehicle v WHERE v.vehicle_key = il.transport_id
)
"""
)
op.execute(
"""
INSERT INTO a76.trailer (trailer_number, tenant_id, company_id)
SELECT DISTINCT ON (il.trailer_num) il.trailer_num, il.tenant_id, il.company_id
FROM a76.invoice_logistics il
WHERE il.trailer_num IS NOT NULL
AND NOT EXISTS (
SELECT 1 FROM a76.trailer tr WHERE tr.trailer_number = il.trailer_num
)
"""
)
# backfill int refs
op.execute(
"""
UPDATE a76.invoice_logistics il
SET carrier_int_id = t.transporter_id
FROM a76.transporter t
WHERE il.carrier_id IS NOT NULL
AND il.carrier_id = t.transporter_key
"""
)
op.execute(
"""
UPDATE a76.invoice_logistics il
SET transport_int_id = v.vehicle_id
FROM a76.vehicle v
WHERE il.transport_id IS NOT NULL
AND il.transport_id = v.vehicle_key
"""
)
op.execute(
"""
UPDATE a76.invoice_logistics il
SET trailer_int_id = tr.trailer_id
FROM a76.trailer tr
WHERE il.trailer_num IS NOT NULL
AND il.trailer_num = tr.trailer_number
"""
)
# FK constraints
op.create_foreign_key(
"fk_a76_invoice_logistics_carrier_int_id_transporter_id",
"invoice_logistics",
"transporter",
["carrier_int_id"],
["transporter_id"],
source_schema="a76",
referent_schema="a76",
ondelete="SET NULL",
)
op.create_foreign_key(
"fk_a76_invoice_logistics_transport_int_id_vehicle_id",
"invoice_logistics",
"vehicle",
["transport_int_id"],
["vehicle_id"],
source_schema="a76",
referent_schema="a76",
ondelete="SET NULL",
)
op.create_foreign_key(
"fk_a76_invoice_logistics_trailer_int_id_trailer_id",
"invoice_logistics",
"trailer",
["trailer_int_id"],
["trailer_id"],
source_schema="a76",
referent_schema="a76",
ondelete="SET NULL",
)
def downgrade() -> None:
pass

View File

@@ -512,15 +512,29 @@ def validate_common(
if invoice.logistics:
if invoice.logistics.carrier_id:
carrier_exists = (
db.query(Transporter)
.filter(
Transporter.id == invoice.logistics.carrier_id,
Transporter.tenant_id == tenant_id,
Transporter.company_id == company_id,
# `carrier_id` is the frontend/export key (string). Prefer validating by the internal int
# when present, otherwise validate by `transporter_key`.
if getattr(invoice.logistics, "carrier_int_id", None) is not None:
carrier_exists = (
db.query(Transporter)
.filter(
Transporter.transporter_id
== invoice.logistics.carrier_int_id,
Transporter.tenant_id == tenant_id,
Transporter.company_id == company_id,
)
.first()
)
else:
carrier_exists = (
db.query(Transporter)
.filter(
Transporter.transporter_key == invoice.logistics.carrier_id,
Transporter.tenant_id == tenant_id,
Transporter.company_id == company_id,
)
.first()
)
.first()
)
if not carrier_exists:
errors.add_error(
field="logistics.carrier_id",

View File

@@ -563,9 +563,15 @@ class InvoiceLogistics(Base, TenantScopedMixin, TimestampMixin):
carrier_id: Mapped[Optional[str]] = mapped_column(
String(10)
) # TRANSPORTISTA / Transportista
carrier_int_id: Mapped[Optional[int]] = mapped_column(
BigInteger, ForeignKey("a76.transporter.transporter_id"), nullable=True
) # Internal surrogate ID for Transporter (by transporter_key)
transport_id: Mapped[Optional[str]] = mapped_column(
String(10)
) # NUMTRAILER / Transportista
transport_int_id: Mapped[Optional[int]] = mapped_column(
BigInteger, ForeignKey("a76.vehicle.vehicle_id"), nullable=True
) # Internal surrogate ID for Vehicle (by vehicle_key)
transport_us_id: Mapped[Optional[str]] = mapped_column(
String(10)
) # TRANSPORTISTAAME / Transportista americano
@@ -601,6 +607,9 @@ class InvoiceLogistics(Base, TenantScopedMixin, TimestampMixin):
trailer_num: Mapped[Optional[str]] = mapped_column(
String(20)
) # NUMTRAILER / Número de trailer
trailer_int_id: Mapped[Optional[int]] = mapped_column(
BigInteger, ForeignKey("a76.trailer.trailer_id"), nullable=True
) # Internal surrogate ID for Trailer (by trailer_number)
seal_number: Mapped[Optional[str]] = mapped_column(
String(15)
) # PRECINTO / Precinto

View File

@@ -299,9 +299,14 @@ class InvoiceFinancialsBase(BaseModel):
class InvoiceLogisticsBase(BaseModel):
"""Base fields for Logistics"""
carrier_id: Optional[str] = Field(None, max_length=10, description="Carrier ID")
carrier_id: Optional[str] = Field(None, max_length=10, description="Carrier code (front-end key)")
carrier_int_id: Optional[int] = Field(
None, description="Internal carrier integer ID (mapped from carrier_id key)"
)
transport_id: Optional[str] = Field(None, max_length=10, description="Transport ID")
transport_int_id: Optional[int] = Field(
None, description="Internal vehicle integer ID (mapped from transport_id key)"
)
transport_us_id: Optional[str] = Field(
None, max_length=10, description="US transport ID"
)
@@ -329,6 +334,9 @@ class InvoiceLogisticsBase(BaseModel):
trailer_num: Optional[str] = Field(
None, max_length=20, description="Trailer number"
)
trailer_int_id: Optional[int] = Field(
None, description="Internal trailer integer ID (mapped from trailer_num key)"
)
seal_number: Optional[str] = Field(None, max_length=15, description="Seal number")
guide_number: Optional[str] = Field(None, max_length=20, description="Guide number")
bill_number: Optional[str] = Field(None, max_length=15, description="Bill number")

View File

@@ -15,6 +15,87 @@ from api.v1.modules.a76.items.models import LineItem
from . import models, schemas
def _autofill_transport_int_ids(
db: Session,
logistics_target,
tenant_id: int,
company_id: int,
) -> None:
"""
Ensures invoice logistics internal int IDs are populated when their string codes exist.
The frontend and exports/imports keep using the string keys/codes (carrier_id, transport_id, trailer_num),
but internally we also persist integer surrogate IDs for fast/consistent joins.
"""
# Local imports to avoid circular dependencies.
from api.v1.modules.a76.transportation.transporters.models import Transporter
from api.v1.modules.a76.transportation.vehicles.models import Vehicle
from api.v1.modules.a76.transportation.trailers.models import Trailer
def _get(field: str):
if isinstance(logistics_target, dict):
return logistics_target.get(field)
return getattr(logistics_target, field, None)
def _set(field: str, value):
if isinstance(logistics_target, dict):
logistics_target[field] = value
else:
setattr(logistics_target, field, value)
carrier_code = _get("carrier_id")
carrier_int_id = _get("carrier_int_id")
if carrier_code and carrier_int_id is None:
transporter_obj = (
db.query(Transporter)
.filter(Transporter.transporter_key == carrier_code)
.first()
)
if not transporter_obj:
# Minimal insert so mapping doesn't end up NULL.
transporter_obj = Transporter(
transporter_key=carrier_code,
tenant_id=tenant_id,
company_id=company_id,
)
db.add(transporter_obj)
db.flush()
_set("carrier_int_id", transporter_obj.transporter_id)
transport_code = _get("transport_id")
transport_int_id = _get("transport_int_id")
if transport_code and transport_int_id is None:
vehicle_obj = (
db.query(Vehicle).filter(Vehicle.vehicle_key == transport_code).first()
)
if not vehicle_obj:
vehicle_obj = Vehicle(
vehicle_key=transport_code,
tenant_id=tenant_id,
company_id=company_id,
)
db.add(vehicle_obj)
db.flush()
_set("transport_int_id", vehicle_obj.vehicle_id)
trailer_code = _get("trailer_num")
trailer_int_id = _get("trailer_int_id")
if trailer_code and trailer_int_id is None:
trailer_obj = (
db.query(Trailer).filter(Trailer.trailer_number == trailer_code).first()
)
if not trailer_obj:
trailer_obj = Trailer(
trailer_number=trailer_code,
tenant_id=tenant_id,
company_id=company_id,
)
db.add(trailer_obj)
db.flush()
_set("trailer_int_id", trailer_obj.trailer_id)
def _get_current_username() -> str:
"""Helper to get current username from context or fallback to System"""
try:
@@ -282,6 +363,12 @@ class InvoiceService:
logistics_dict["invoice_id"] = new_invoice.id
logistics_dict["tenant_id"] = tenant_id
logistics_dict["company_id"] = company_id
_autofill_transport_int_ids(
db,
logistics_dict,
tenant_id=tenant_id,
company_id=company_id,
)
new_logistics = models.InvoiceLogistics(**logistics_dict)
db.add(new_logistics)
@@ -444,11 +531,23 @@ class InvoiceService:
if value == "":
value = None
setattr(invoice.logistics, key, value)
_autofill_transport_int_ids(
db,
invoice.logistics,
tenant_id=tenant_id,
company_id=company_id,
)
else:
logistics_dict = invoice_data.logistics.model_dump()
logistics_dict["invoice_id"] = invoice.id
logistics_dict["tenant_id"] = tenant_id
logistics_dict["company_id"] = company_id
_autofill_transport_int_ids(
db,
logistics_dict,
tenant_id=tenant_id,
company_id=company_id,
)
new_logistics = models.InvoiceLogistics(**logistics_dict)
db.add(new_logistics)

View File

@@ -6071,8 +6071,30 @@ def _do_insert_valid_rows(job_id: str, model_target: str, job_type_override: Opt
transport_type = TransportType(transport_str)
except ValueError:
transport_type = TransportType.NONE
carrier_code = row_norm.get('CLAVE TRANSPORTISTA') or None
carrier_int_id = None
if carrier_code:
# Internal int mapping for carrier_code -> transporter_id
from api.v1.modules.a76.transportation.transporters.models import Transporter
carrier_obj = (
session.query(Transporter)
.filter(Transporter.transporter_key == carrier_code)
.first()
)
if not carrier_obj:
carrier_obj = Transporter(
transporter_key=carrier_code,
tenant_id=tenant_id,
company_id=company_id,
)
session.add(carrier_obj)
session.flush()
carrier_int_id = carrier_obj.transporter_id
logistics = InvoiceLogistics(
carrier_id=(row_norm.get('CLAVE TRANSPORTISTA') or None),
carrier_id=carrier_code,
carrier_int_id=carrier_int_id,
driver_name=(row_norm.get('NOMBRE CONDUCTOR') or None),
transport_type=transport_type,
transport_num=(row_norm.get('NUMERO TRANSPORTE') or None),

View File

@@ -298,7 +298,13 @@ class ConsolidadoImportacionMexService:
if logistics:
# 1. Transporter (CAAT / SCAC)
if logistics.carrier_id:
if logistics.carrier_int_id is not None:
transporter_obj = (
db.query(Transporter)
.filter(Transporter.transporter_id == logistics.carrier_int_id)
.first()
)
elif logistics.carrier_id:
transporter_obj = (
db.query(Transporter)
.filter(Transporter.transporter_key == logistics.carrier_id)
@@ -342,11 +348,18 @@ class ConsolidadoImportacionMexService:
# 2. Vehicle (Placas Tracto) - Try transport_id first
if logistics.transport_id:
veh_obj = (
db.query(Vehicle)
.filter(Vehicle.vehicle_key == logistics.transport_id)
.first()
)
if logistics.transport_int_id is not None:
veh_obj = (
db.query(Vehicle)
.filter(Vehicle.vehicle_id == logistics.transport_int_id)
.first()
)
else:
veh_obj = (
db.query(Vehicle)
.filter(Vehicle.vehicle_key == logistics.transport_id)
.first()
)
if veh_obj:
placas_val = veh_obj.plate_number or placas_val
elif (
@@ -362,11 +375,18 @@ class ConsolidadoImportacionMexService:
# 3. Trailer (Placas Remolque)
if logistics.trailer_num:
trl_obj = (
db.query(Trailer)
.filter(Trailer.trailer_number == logistics.trailer_num)
.first()
)
if logistics.trailer_int_id is not None:
trl_obj = (
db.query(Trailer)
.filter(Trailer.trailer_id == logistics.trailer_int_id)
.first()
)
else:
trl_obj = (
db.query(Trailer)
.filter(Trailer.trailer_number == logistics.trailer_num)
.first()
)
if trl_obj:
placas_remolque_val = trl_obj.plate_number or ""

View File

@@ -175,8 +175,16 @@ class ConsolidadoImportacionMexService:
if logistics:
# 1. Transporter (CAAT / SCAC)
if logistics.carrier_id:
transporter_obj = db.query(Transporter).filter(Transporter.transporter_key == logistics.carrier_id).first()
if logistics.carrier_int_id is not None:
transporter_obj = (
db.query(Transporter)
.filter(Transporter.transporter_id == logistics.carrier_int_id)
.first()
)
elif logistics.carrier_id:
transporter_obj = db.query(Transporter).filter(
Transporter.transporter_key == logistics.carrier_id
).first()
if transporter_obj:
caat_val = transporter_obj.caat_code or ""
scac_val = transporter_obj.transport_code or "" # Mapping transport_code to SCAC
@@ -213,7 +221,16 @@ class ConsolidadoImportacionMexService:
# 2. Vehicle (Placas Tracto) - Try transport_id first
if logistics.transport_id:
veh_obj = db.query(Vehicle).filter(Vehicle.vehicle_key == logistics.transport_id).first()
if logistics.transport_int_id is not None:
veh_obj = (
db.query(Vehicle)
.filter(Vehicle.vehicle_id == logistics.transport_int_id)
.first()
)
else:
veh_obj = db.query(Vehicle).filter(
Vehicle.vehicle_key == logistics.transport_id
).first()
if veh_obj:
placas_val = veh_obj.plate_number or placas_val
elif logistics.vehicle_num: # Fallback to vehicle_num if populated and transport_id failed/empty
@@ -223,7 +240,16 @@ class ConsolidadoImportacionMexService:
# 3. Trailer (Placas Remolque)
if logistics.trailer_num:
trl_obj = db.query(Trailer).filter(Trailer.trailer_number == logistics.trailer_num).first()
if logistics.trailer_int_id is not None:
trl_obj = (
db.query(Trailer)
.filter(Trailer.trailer_id == logistics.trailer_int_id)
.first()
)
else:
trl_obj = db.query(Trailer).filter(
Trailer.trailer_number == logistics.trailer_num
).first()
if trl_obj:
placas_remolque_val = trl_obj.plate_number or ""

View File

@@ -317,7 +317,17 @@ class FacturaImportacionMexService:
if logistics:
# 1. Transporter (CAAT / SCAC)
if logistics.carrier_id:
if logistics.carrier_int_id is not None:
transporter_obj = (
db.query(Transporter)
.filter(Transporter.transporter_id == logistics.carrier_int_id)
.first()
)
if transporter_obj:
caat_val = transporter_obj.caat_code or ""
scac_val = transporter_obj.transport_code or ""
transportista_val = transporter_obj.name or logistics.carrier_id
elif logistics.carrier_id:
transporter_obj = (
db.query(Transporter)
.filter(Transporter.transporter_key == logistics.carrier_id)
@@ -332,11 +342,18 @@ class FacturaImportacionMexService:
# 2. Vehicle (Placas Tracto) - Try transport_id first
if logistics.transport_id:
veh_obj = (
db.query(Vehicle)
.filter(Vehicle.vehicle_key == logistics.transport_id)
.first()
)
if logistics.transport_int_id is not None:
veh_obj = (
db.query(Vehicle)
.filter(Vehicle.vehicle_id == logistics.transport_int_id)
.first()
)
else:
veh_obj = (
db.query(Vehicle)
.filter(Vehicle.vehicle_key == logistics.transport_id)
.first()
)
if veh_obj:
placas_val = veh_obj.plate_number or placas_val
elif (
@@ -352,11 +369,18 @@ class FacturaImportacionMexService:
# 3. Trailer (Placas Remolque)
if logistics.trailer_num:
trl_obj = (
db.query(Trailer)
.filter(Trailer.trailer_number == logistics.trailer_num)
.first()
)
if logistics.trailer_int_id is not None:
trl_obj = (
db.query(Trailer)
.filter(Trailer.trailer_id == logistics.trailer_int_id)
.first()
)
else:
trl_obj = (
db.query(Trailer)
.filter(Trailer.trailer_number == logistics.trailer_num)
.first()
)
if trl_obj:
placas_remolque_val = trl_obj.plate_number or ""

View File

@@ -166,8 +166,16 @@ class FacturaImportacionMexService:
if logistics:
# 1. Transporter (CAAT / SCAC)
if logistics.carrier_id:
transporter_obj = db.query(Transporter).filter(Transporter.transporter_key == logistics.carrier_id).first()
if logistics.carrier_int_id is not None:
transporter_obj = (
db.query(Transporter)
.filter(Transporter.transporter_id == logistics.carrier_int_id)
.first()
)
elif logistics.carrier_id:
transporter_obj = db.query(Transporter).filter(
Transporter.transporter_key == logistics.carrier_id
).first()
if transporter_obj:
caat_val = transporter_obj.caat_code or ""
scac_val = transporter_obj.transport_code or "" # Mapping transport_code to SCAC
@@ -175,7 +183,16 @@ class FacturaImportacionMexService:
# 2. Vehicle (Placas Tracto) - Try transport_id first
if logistics.transport_id:
veh_obj = db.query(Vehicle).filter(Vehicle.vehicle_key == logistics.transport_id).first()
if logistics.transport_int_id is not None:
veh_obj = (
db.query(Vehicle)
.filter(Vehicle.vehicle_id == logistics.transport_int_id)
.first()
)
else:
veh_obj = db.query(Vehicle).filter(
Vehicle.vehicle_key == logistics.transport_id
).first()
if veh_obj:
placas_val = veh_obj.plate_number or placas_val
elif logistics.vehicle_num: # Fallback to vehicle_num if populated and transport_id failed/empty
@@ -185,7 +202,16 @@ class FacturaImportacionMexService:
# 3. Trailer (Placas Remolque)
if logistics.trailer_num:
trl_obj = db.query(Trailer).filter(Trailer.trailer_number == logistics.trailer_num).first()
if logistics.trailer_int_id is not None:
trl_obj = (
db.query(Trailer)
.filter(Trailer.trailer_id == logistics.trailer_int_id)
.first()
)
else:
trl_obj = db.query(Trailer).filter(
Trailer.trailer_number == logistics.trailer_num
).first()
if trl_obj:
placas_remolque_val = trl_obj.plate_number or ""

View File

@@ -317,7 +317,17 @@ class FacturaImportacionUsaService:
if logistics:
# 1. Transporter (CAAT / SCAC)
if logistics.carrier_id:
if logistics.carrier_int_id is not None:
transporter_obj = (
db.query(Transporter)
.filter(Transporter.transporter_id == logistics.carrier_int_id)
.first()
)
if transporter_obj:
caat_val = transporter_obj.caat_code or ""
scac_val = transporter_obj.transport_code or ""
transportista_val = transporter_obj.name or logistics.carrier_id
elif logistics.carrier_id:
transporter_obj = (
db.query(Transporter)
.filter(Transporter.transporter_key == logistics.carrier_id)
@@ -325,18 +335,23 @@ class FacturaImportacionUsaService:
)
if transporter_obj:
caat_val = transporter_obj.caat_code or ""
scac_val = (
transporter_obj.transport_code or ""
) # Mapping transport_code to SCAC
scac_val = transporter_obj.transport_code or ""
transportista_val = transporter_obj.name or logistics.carrier_id
# 2. Vehicle (Plates)
if logistics.transport_id:
veh_obj = (
db.query(Vehicle)
.filter(Vehicle.vehicle_key == logistics.transport_id)
.first()
)
if logistics.transport_int_id is not None:
veh_obj = (
db.query(Vehicle)
.filter(Vehicle.vehicle_id == logistics.transport_int_id)
.first()
)
else:
veh_obj = (
db.query(Vehicle)
.filter(Vehicle.vehicle_key == logistics.transport_id)
.first()
)
if veh_obj:
placas_val = veh_obj.plate_number or placas_val
elif logistics.vehicle_num:
@@ -350,11 +365,18 @@ class FacturaImportacionUsaService:
# 3. Trailer
if logistics.trailer_num:
trl_obj = (
db.query(Trailer)
.filter(Trailer.trailer_number == logistics.trailer_num)
.first()
)
if logistics.trailer_int_id is not None:
trl_obj = (
db.query(Trailer)
.filter(Trailer.trailer_id == logistics.trailer_int_id)
.first()
)
else:
trl_obj = (
db.query(Trailer)
.filter(Trailer.trailer_number == logistics.trailer_num)
.first()
)
if trl_obj:
placas_remolque_val = trl_obj.plate_number or ""

View File

@@ -186,8 +186,18 @@ class PackingListService:
if logistics:
# 1. Transporter (CAAT / SCAC)
if logistics.carrier_id:
transporter_obj = db.query(Transporter).filter(Transporter.transporter_key == logistics.carrier_id).first()
if logistics.carrier_int_id is not None:
transporter_obj = (
db.query(Transporter)
.filter(Transporter.transporter_id == logistics.carrier_int_id)
.first()
)
elif logistics.carrier_id:
transporter_obj = (
db.query(Transporter)
.filter(Transporter.transporter_key == logistics.carrier_id)
.first()
)
if transporter_obj:
caat_val = transporter_obj.caat_code or ""
scac_val = transporter_obj.transport_code or ""
@@ -195,7 +205,18 @@ class PackingListService:
# 2. Vehicle (Placas Tracto)
if logistics.transport_id:
veh_obj = db.query(Vehicle).filter(Vehicle.vehicle_key == logistics.transport_id).first()
if logistics.transport_int_id is not None:
veh_obj = (
db.query(Vehicle)
.filter(Vehicle.vehicle_id == logistics.transport_int_id)
.first()
)
else:
veh_obj = (
db.query(Vehicle)
.filter(Vehicle.vehicle_key == logistics.transport_id)
.first()
)
if veh_obj:
placas_val = veh_obj.plate_number or placas_val
elif logistics.vehicle_num:
@@ -205,7 +226,18 @@ class PackingListService:
# 3. Trailer (Placas Remolque)
if logistics.trailer_num:
trl_obj = db.query(Trailer).filter(Trailer.trailer_number == logistics.trailer_num).first()
if logistics.trailer_int_id is not None:
trl_obj = (
db.query(Trailer)
.filter(Trailer.trailer_id == logistics.trailer_int_id)
.first()
)
else:
trl_obj = (
db.query(Trailer)
.filter(Trailer.trailer_number == logistics.trailer_num)
.first()
)
if trl_obj:
placas_remolque_val = trl_obj.plate_number or ""

View File

@@ -5,6 +5,7 @@ from pydantic import BaseModel
class DriverBaseDTO(BaseModel):
transporter_key: str
driver_id: Optional[int] = None
line: int
driver_name: Optional[str] = None
license_number: Optional[str] = None

View File

@@ -1,6 +1,6 @@
from api.v1.common.base_models import TenantScopedMixin, TimestampMixin
from core.database import Base
from sqlalchemy import Column, ForeignKey, ForeignKeyConstraint, Integer, String
from sqlalchemy import BigInteger, Column, ForeignKey, ForeignKeyConstraint, Integer, String
class Driver(Base, TenantScopedMixin, TimestampMixin):
@@ -16,6 +16,8 @@ class Driver(Base, TenantScopedMixin, TimestampMixin):
nullable=False,
)
line = Column(Integer, primary_key=True, nullable=False)
# Internal integer surrogate ID. The frontend continues to use transporter_key/line.
driver_id = Column(BigInteger, nullable=False, unique=True, index=True)
driver_name = Column(String(80), nullable=True)
license_number = Column(String(29), nullable=True)
express_line_id = Column(String(17), nullable=True)

View File

@@ -5,6 +5,7 @@ from pydantic import BaseModel, Field
class TrailerBaseDTO(BaseModel):
trailer_number: str = Field(..., description="Trailer number (primary identifier)")
trailer_id: Optional[int] = Field(None, description="Internal trailer integer ID")
ace_trailer_number: Optional[str] = None
trailer_type_key: Optional[str] = None
seal: Optional[str] = None

View File

@@ -1,6 +1,6 @@
from api.v1.common.base_models import TenantScopedMixin, TimestampMixin
from core.database import Base
from sqlalchemy import Column, ForeignKey, ForeignKeyConstraint, String
from sqlalchemy import BigInteger, Column, ForeignKey, ForeignKeyConstraint, String
class Trailer(Base, TenantScopedMixin, TimestampMixin):
@@ -10,6 +10,8 @@ class Trailer(Base, TenantScopedMixin, TimestampMixin):
)
trailer_number = Column(String(20), primary_key=True, nullable=False)
# Internal integer identifier (surrogate). The frontend continues to use trailer_number.
trailer_id = Column(BigInteger, nullable=False, unique=True, index=True)
ace_trailer_number = Column(String(10), nullable=True)
trailer_type_key = Column(
String(2), ForeignKey("public.trailer_type.trailer_type_key"), nullable=True

View File

@@ -5,6 +5,7 @@ from pydantic import BaseModel, Field
class TransporterBaseDTO(BaseModel):
transporter_key: str = Field(..., description="Transporter key (primary identifier)")
transporter_id: Optional[int] = Field(None, description="Internal transporter integer ID")
name: Optional[str] = None
short_name: Optional[str] = None
responsible: Optional[str] = None

View File

@@ -1,6 +1,6 @@
from api.v1.common.base_models import TenantScopedMixin, TimestampMixin
from core.database import Base
from sqlalchemy import Column, ForeignKeyConstraint, String
from sqlalchemy import BigInteger, Column, ForeignKeyConstraint, String
class Transporter(Base, TenantScopedMixin, TimestampMixin):
@@ -10,6 +10,8 @@ class Transporter(Base, TenantScopedMixin, TimestampMixin):
)
transporter_key = Column(String(23), primary_key=True, nullable=False)
# Internal integer identifier (surrogate). The frontend continues to use transporter_key.
transporter_id = Column(BigInteger, nullable=False, unique=True, index=True)
name = Column(String(256), nullable=True)
short_name = Column(String(10), nullable=True)
responsible = Column(String(100), nullable=True)

View File

@@ -5,6 +5,7 @@ from pydantic import BaseModel, Field
class VehicleBaseDTO(BaseModel):
vehicle_key: str = Field(..., description="Vehicle key (primary identifier)")
vehicle_id: Optional[int] = Field(None, description="Internal vehicle integer ID")
ace_vehicle_key: Optional[str] = None
transporter_key: Optional[str] = None
transport_identifier: Optional[str] = None

View File

@@ -1,6 +1,6 @@
from api.v1.common.base_models import TenantScopedMixin, TimestampMixin
from core.database import Base
from sqlalchemy import DECIMAL, Column, Integer, String
from sqlalchemy import DECIMAL, Column, Integer, BigInteger, String
class Vehicle(Base, TenantScopedMixin, TimestampMixin):
@@ -10,6 +10,8 @@ class Vehicle(Base, TenantScopedMixin, TimestampMixin):
)
vehicle_key = Column(String(14), primary_key=True, nullable=False)
# Internal integer identifier (surrogate). The frontend continues to use vehicle_key.
vehicle_id = Column(BigInteger, nullable=False, unique=True, index=True)
ace_vehicle_key = Column(String(10), nullable=True)
transporter_key = Column(String(23), nullable=True)
transport_identifier = Column(String(30), nullable=True)