"""Modelos del módulo Tarifario (base de costos para Cotizaciones). Un ``RateSheet`` (tarifario) pertenece a un proveedor y agrupa muchas ``RateLane`` (rutas origen→destino). Cada ruta tiene, según el modo: - Aéreo / LCL: varios ``RateBreak`` (quiebres de peso/volumen con su tarifa). - FCL / terrestre: una tarifa plana por contenedor/unidad (``flat_rate``). Los ``RateCharge`` son cargos adicionales a nivel tarifario o ruta. """ from datetime import date from decimal import Decimal from sqlalchemy import Date, ForeignKey, Integer, Numeric, String, Text, text from sqlalchemy.orm import Mapped, mapped_column from api.v1.common.base_models import TenantScopedMixin, TimestampMixin from core.database import Base class RateSheet(Base, TenantScopedMixin, TimestampMixin): __tablename__ = "rate_sheets" __table_args__ = {"schema": "crm"} id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) supplier_id: Mapped[int | None] = mapped_column( Integer, ForeignKey("crm.suppliers.id"), nullable=True, index=True ) # aereo | maritimo_fcl | maritimo_lcl | terrestre mode: Mapped[str] = mapped_column(String(20), nullable=False, index=True) name: Mapped[str] = mapped_column(String(255), nullable=False) currency: Mapped[str | None] = mapped_column(String(3), nullable=True, server_default=text("'USD'")) valid_from: Mapped[date | None] = mapped_column(Date, nullable=True) valid_to: Mapped[date | None] = mapped_column(Date, nullable=True) default_origin: Mapped[str | None] = mapped_column(String(20), nullable=True) # borrador | activo | vencido | reemplazado status: Mapped[str] = mapped_column(String(20), nullable=False, server_default=text("'borrador'")) source_file: Mapped[str | None] = mapped_column(String(512), nullable=True) source_url: Mapped[str | None] = mapped_column(String(1024), nullable=True) notes: Mapped[str | None] = mapped_column(Text, nullable=True) created_by: Mapped[str | None] = mapped_column(String(64), nullable=True) updated_by: Mapped[str | None] = mapped_column(String(64), nullable=True) class RateLane(Base, TenantScopedMixin, TimestampMixin): __tablename__ = "rate_lanes" __table_args__ = {"schema": "crm"} id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) rate_sheet_id: Mapped[int] = mapped_column( Integer, ForeignKey("crm.rate_sheets.id"), nullable=False, index=True ) origin: Mapped[str | None] = mapped_column(String(20), nullable=True, index=True) destination: Mapped[str | None] = mapped_column(String(20), nullable=True, index=True) region: Mapped[str | None] = mapped_column(String(60), nullable=True) # Solo FCL/terrestre (código del catálogo tipo_equipo). Nulo en aéreo/LCL. equipment_type: Mapped[str | None] = mapped_column(String(20), nullable=True) # per_kg | per_wm | per_container | flat rate_unit: Mapped[str | None] = mapped_column(String(20), nullable=True) min_charge: Mapped[Decimal | None] = mapped_column(Numeric(14, 4), nullable=True) flat_rate: Mapped[Decimal | None] = mapped_column(Numeric(14, 4), nullable=True) transit_days: Mapped[int | None] = mapped_column(Integer, nullable=True) notes: Mapped[str | None] = mapped_column(Text, nullable=True) class RateBreak(Base, TenantScopedMixin, TimestampMixin): __tablename__ = "rate_breaks" __table_args__ = {"schema": "crm"} id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) rate_lane_id: Mapped[int] = mapped_column( Integer, ForeignKey("crm.rate_lanes.id"), nullable=False, index=True ) # Umbral del quiebre (kg en aéreo; W/M en LCL) from_qty: Mapped[Decimal] = mapped_column(Numeric(12, 3), nullable=False, server_default=text("0")) rate: Mapped[Decimal] = mapped_column(Numeric(14, 4), nullable=False, server_default=text("0")) class RateCharge(Base, TenantScopedMixin, TimestampMixin): __tablename__ = "rate_charges" __table_args__ = {"schema": "crm"} id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) rate_sheet_id: Mapped[int | None] = mapped_column( Integer, ForeignKey("crm.rate_sheets.id"), nullable=True, index=True ) rate_lane_id: Mapped[int | None] = mapped_column( Integer, ForeignKey("crm.rate_lanes.id"), nullable=True, index=True ) concept: Mapped[str] = mapped_column(String(60), nullable=False) # fijo | por_kg | por_guia | por_contenedor | porcentaje charge_type: Mapped[str] = mapped_column(String(20), nullable=False, server_default=text("'fijo'")) value: Mapped[Decimal | None] = mapped_column(Numeric(14, 4), nullable=True) condition: Mapped[str | None] = mapped_column(Text, nullable=True)