- 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>
132 lines
5.9 KiB
Python
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)
|