- Modalidad AÉREO (4ª opción en "¿Cómo desea cotizar?"): en la solicitud muestra la sección aérea con el cálculo en vivo P/Vol = (L×A×H cm × bultos)/6000 y el peso a cobrar = max(peso bruto, P/Vol). Fija el transporte en aéreo. - Utilidad compartida crm/common/pricing.py (air_volumetric_kg / air_chargeable_kg, factor internacional 6000). - Motor de costeo (rates): la rama aérea usa el P/Vol por dimensiones si vienen (CostRequest ahora acepta length/width/height_cm); respaldo m³×167 cuando no. - Cotizador: captura por dimensiones (L×A×H + bultos) en modo aéreo y muestra el P/Vol. - Solicitud→Cotización: si es AÉREO, siembra el concepto de flete con cantidad = peso a cobrar (P/Vol) para capturar la tarifa por kg. - Pruebas: test_pricing (ejemplo del doc → 720; max bruto/volumétrico) + cotización aérea desde solicitud. Suite en verde (108). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
179 lines
4.8 KiB
Python
179 lines
4.8 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
|
||
|
||
|
||
class RateChargeCreate(BaseModel):
|
||
concept: str = Field(..., max_length=60)
|
||
charge_type: str = Field("fijo", max_length=20)
|
||
value: Decimal | None = None
|
||
condition: str | None = None
|
||
rate_lane_id: int | None = None
|
||
|
||
|
||
class RateChargeUpdate(BaseModel):
|
||
concept: str | None = Field(None, max_length=60)
|
||
charge_type: str | None = Field(None, max_length=20)
|
||
value: Decimal | None = None
|
||
condition: str | None = None
|
||
|
||
|
||
class RateChargeResponse(BaseModel):
|
||
model_config = ConfigDict(from_attributes=True)
|
||
id: int
|
||
rate_sheet_id: int | None
|
||
rate_lane_id: int | None
|
||
concept: str
|
||
charge_type: str
|
||
value: Decimal | None
|
||
condition: str | 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
|
||
# Dimensiones (cm) para el peso volumétrico aéreo (P/Vol = L×A×H×cant / 6000)
|
||
length_cm: Decimal | None = None
|
||
width_cm: Decimal | None = None
|
||
height_cm: 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]
|