Files
CRM_AGENTES_CARGA/backend/alembic/versions/f8a9b0c1d2e3_crm_rates.py
Ernesto Herrera 2ae6901b6a feat(crm): módulo Tarifario — modelos, CRUD, import Excel y motor de costeo
- 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>
2026-07-27 09:28:49 -06:00

132 lines
5.9 KiB
Python

"""crm rates: tarifarios (rate_sheets/lanes/breaks/charges)
Revision ID: f8a9b0c1d2e3
Revises: e7f8a9b0c1d2
Create Date: 2026-07-27 00:00:00.000000
Módulo Tarifario: base de costos para Cotizaciones (import por Excel + motor de costeo).
"""
from typing import Sequence, Union
import sqlalchemy as sa
from alembic import op
revision: str = "f8a9b0c1d2e3"
down_revision: Union[str, None] = "e7f8a9b0c1d2"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
SCHEMA = "crm"
def _scoped() -> list[sa.Column]:
return [
sa.Column("tenant_id", sa.Integer(), nullable=False),
sa.Column("company_id", sa.Integer(), nullable=False),
sa.Column("created_at", sa.DateTime(), nullable=False, server_default=sa.text("now()")),
sa.Column("updated_at", sa.DateTime(), nullable=False, server_default=sa.text("now()")),
sa.Column("deleted_at", sa.DateTime(), nullable=True),
]
def _idx(table: str) -> None:
op.create_index(f"ix_{SCHEMA}_{table}_id", table, ["id"], schema=SCHEMA)
op.create_index(f"ix_{SCHEMA}_{table}_tenant_id", table, ["tenant_id"], schema=SCHEMA)
op.create_index(f"ix_{SCHEMA}_{table}_company_id", table, ["company_id"], schema=SCHEMA)
def upgrade() -> None:
# ----- rate_sheets -----
op.create_table(
"rate_sheets",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("supplier_id", sa.Integer(), nullable=True),
sa.Column("mode", sa.String(length=20), nullable=False),
sa.Column("name", sa.String(length=255), nullable=False),
sa.Column("currency", sa.String(length=3), nullable=True, server_default=sa.text("'USD'")),
sa.Column("valid_from", sa.Date(), nullable=True),
sa.Column("valid_to", sa.Date(), nullable=True),
sa.Column("default_origin", sa.String(length=20), nullable=True),
sa.Column("status", sa.String(length=20), nullable=False, server_default=sa.text("'borrador'")),
sa.Column("source_file", sa.String(length=512), nullable=True),
sa.Column("source_url", sa.String(length=1024), nullable=True),
sa.Column("notes", sa.Text(), nullable=True),
sa.Column("created_by", sa.String(length=64), nullable=True),
sa.Column("updated_by", sa.String(length=64), nullable=True),
*_scoped(),
sa.PrimaryKeyConstraint("id"),
sa.ForeignKeyConstraint(["tenant_id"], ["core.tenants.id"]),
sa.ForeignKeyConstraint(["supplier_id"], [f"{SCHEMA}.suppliers.id"]),
schema=SCHEMA,
)
_idx("rate_sheets")
op.create_index("ix_crm_rate_sheets_mode", "rate_sheets", ["mode"], schema=SCHEMA)
op.create_index("ix_crm_rate_sheets_supplier_id", "rate_sheets", ["supplier_id"], schema=SCHEMA)
# ----- rate_lanes -----
op.create_table(
"rate_lanes",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("rate_sheet_id", sa.Integer(), nullable=False),
sa.Column("origin", sa.String(length=20), nullable=True),
sa.Column("destination", sa.String(length=20), nullable=True),
sa.Column("region", sa.String(length=60), nullable=True),
sa.Column("equipment_type", sa.String(length=20), nullable=True),
sa.Column("rate_unit", sa.String(length=20), nullable=True),
sa.Column("min_charge", sa.Numeric(precision=14, scale=4), nullable=True),
sa.Column("flat_rate", sa.Numeric(precision=14, scale=4), nullable=True),
sa.Column("transit_days", sa.Integer(), nullable=True),
sa.Column("notes", sa.Text(), nullable=True),
*_scoped(),
sa.PrimaryKeyConstraint("id"),
sa.ForeignKeyConstraint(["tenant_id"], ["core.tenants.id"]),
sa.ForeignKeyConstraint(["rate_sheet_id"], [f"{SCHEMA}.rate_sheets.id"]),
schema=SCHEMA,
)
_idx("rate_lanes")
op.create_index("ix_crm_rate_lanes_rate_sheet_id", "rate_lanes", ["rate_sheet_id"], schema=SCHEMA)
op.create_index("ix_crm_rate_lanes_origin", "rate_lanes", ["origin"], schema=SCHEMA)
op.create_index("ix_crm_rate_lanes_destination", "rate_lanes", ["destination"], schema=SCHEMA)
# ----- rate_breaks -----
op.create_table(
"rate_breaks",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("rate_lane_id", sa.Integer(), nullable=False),
sa.Column("from_qty", sa.Numeric(precision=12, scale=3), nullable=False, server_default=sa.text("0")),
sa.Column("rate", sa.Numeric(precision=14, scale=4), nullable=False, server_default=sa.text("0")),
*_scoped(),
sa.PrimaryKeyConstraint("id"),
sa.ForeignKeyConstraint(["tenant_id"], ["core.tenants.id"]),
sa.ForeignKeyConstraint(["rate_lane_id"], [f"{SCHEMA}.rate_lanes.id"]),
schema=SCHEMA,
)
_idx("rate_breaks")
op.create_index("ix_crm_rate_breaks_rate_lane_id", "rate_breaks", ["rate_lane_id"], schema=SCHEMA)
# ----- rate_charges -----
op.create_table(
"rate_charges",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("rate_sheet_id", sa.Integer(), nullable=True),
sa.Column("rate_lane_id", sa.Integer(), nullable=True),
sa.Column("concept", sa.String(length=60), nullable=False),
sa.Column("charge_type", sa.String(length=20), nullable=False, server_default=sa.text("'fijo'")),
sa.Column("value", sa.Numeric(precision=14, scale=4), nullable=True),
sa.Column("condition", sa.Text(), nullable=True),
*_scoped(),
sa.PrimaryKeyConstraint("id"),
sa.ForeignKeyConstraint(["tenant_id"], ["core.tenants.id"]),
sa.ForeignKeyConstraint(["rate_sheet_id"], [f"{SCHEMA}.rate_sheets.id"]),
sa.ForeignKeyConstraint(["rate_lane_id"], [f"{SCHEMA}.rate_lanes.id"]),
schema=SCHEMA,
)
_idx("rate_charges")
def downgrade() -> None:
op.drop_table("rate_charges", schema=SCHEMA)
op.drop_table("rate_breaks", schema=SCHEMA)
op.drop_table("rate_lanes", schema=SCHEMA)
op.drop_table("rate_sheets", schema=SCHEMA)