- Tablas rate_sheets/lanes/breaks/charges (esquema crm) + migración con down(). - Catálogos nuevos: modo_tarifario, unidad_tarifa, concepto_cargo. - CRUD de tarifarios y rutas; descarga de plantilla Excel por modo; import con vista previa y validación; alta directa desde Excel. - Motor de costeo /rate-quote: aéreo (peso facturable + quiebres + optimización), marítimo FCL (por contenedor), LCL (W/M) y terrestre; suma cargos adicionales. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
149 lines
3.9 KiB
Python
149 lines
3.9 KiB
Python
"""Schemas del módulo Tarifario."""
|
|
|
|
from datetime import date, datetime
|
|
from decimal import Decimal
|
|
|
|
from pydantic import BaseModel, ConfigDict, Field
|
|
|
|
|
|
# ---------- Quiebres y cargos ----------
|
|
class RateBreakDTO(BaseModel):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
from_qty: Decimal = Field(0)
|
|
rate: Decimal = Field(0)
|
|
|
|
|
|
class RateChargeDTO(BaseModel):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
concept: str = Field(..., max_length=60)
|
|
charge_type: str = Field("fijo", max_length=20)
|
|
value: Decimal | None = None
|
|
condition: str | None = None
|
|
|
|
|
|
# ---------- Rutas ----------
|
|
class RateLaneBase(BaseModel):
|
|
origin: str | None = Field(None, max_length=20)
|
|
destination: str | None = Field(None, max_length=20)
|
|
region: str | None = Field(None, max_length=60)
|
|
equipment_type: str | None = Field(None, max_length=20)
|
|
rate_unit: str | None = Field(None, max_length=20)
|
|
min_charge: Decimal | None = None
|
|
flat_rate: Decimal | None = None
|
|
transit_days: int | None = None
|
|
notes: str | None = None
|
|
|
|
|
|
class RateLaneCreate(RateLaneBase):
|
|
breaks: list[RateBreakDTO] = Field(default_factory=list)
|
|
|
|
|
|
class RateLaneUpdate(RateLaneBase):
|
|
breaks: list[RateBreakDTO] | None = None
|
|
|
|
|
|
class RateLaneResponse(RateLaneBase):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
id: int
|
|
rate_sheet_id: int
|
|
breaks: list[RateBreakDTO] = Field(default_factory=list)
|
|
|
|
|
|
# ---------- Tarifario (cabecera) ----------
|
|
class RateSheetBase(BaseModel):
|
|
supplier_id: int | None = None
|
|
mode: str = Field(..., max_length=20)
|
|
name: str = Field(..., min_length=1, max_length=255)
|
|
currency: str | None = Field("USD", max_length=3)
|
|
valid_from: date | None = None
|
|
valid_to: date | None = None
|
|
default_origin: str | None = Field(None, max_length=20)
|
|
status: str = Field("borrador", max_length=20)
|
|
notes: str | None = None
|
|
|
|
|
|
class RateSheetCreate(RateSheetBase):
|
|
pass
|
|
|
|
|
|
class RateSheetUpdate(BaseModel):
|
|
supplier_id: int | None = None
|
|
mode: str | None = Field(None, max_length=20)
|
|
name: str | None = Field(None, max_length=255)
|
|
currency: str | None = Field(None, max_length=3)
|
|
valid_from: date | None = None
|
|
valid_to: date | None = None
|
|
default_origin: str | None = Field(None, max_length=20)
|
|
status: str | None = Field(None, max_length=20)
|
|
notes: str | None = None
|
|
|
|
|
|
class RateSheetResponse(RateSheetBase):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
id: int
|
|
tenant_id: int
|
|
company_id: int
|
|
source_file: str | None = None
|
|
created_by: str | None = None
|
|
updated_by: str | None = None
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
lane_count: int | None = None
|
|
|
|
|
|
# ---------- Importación ----------
|
|
class ImportPreviewRow(BaseModel):
|
|
row: int
|
|
data: dict
|
|
ok: bool
|
|
warnings: list[str] = Field(default_factory=list)
|
|
errors: list[str] = Field(default_factory=list)
|
|
|
|
|
|
class ImportPreview(BaseModel):
|
|
mode: str
|
|
total: int
|
|
valid: int
|
|
rows: list[ImportPreviewRow]
|
|
columns: list[str]
|
|
|
|
|
|
class ImportConfirm(RateSheetCreate):
|
|
lanes: list[RateLaneCreate]
|
|
|
|
|
|
# ---------- Costeo ----------
|
|
class CostRequest(BaseModel):
|
|
mode: str
|
|
origin: str | None = None
|
|
destination: str | None = None
|
|
on_date: date | None = None
|
|
gross_weight_kg: Decimal | None = None
|
|
volume_m3: Decimal | None = None
|
|
equipment_type: str | None = None
|
|
quantity: int = 1
|
|
dangerous: bool = False
|
|
|
|
|
|
class CostChargeLine(BaseModel):
|
|
concept: str
|
|
amount: Decimal
|
|
|
|
|
|
class CostOption(BaseModel):
|
|
rate_sheet_id: int
|
|
rate_sheet_name: str
|
|
supplier_id: int | None
|
|
currency: str | None
|
|
chargeable: Decimal | None = None # peso/wm facturable usado
|
|
base_cost: Decimal
|
|
charges: list[CostChargeLine] = Field(default_factory=list)
|
|
total_cost: Decimal
|
|
transit_days: int | None = None
|
|
detail: str | None = None
|
|
|
|
|
|
class CostResult(BaseModel):
|
|
request: CostRequest
|
|
options: list[CostOption]
|