- Tabla crm.cases (expediente) con folio EXP2026-08-001 (next_folio entidad EXP,
sin dirección). Nace al crear la Oportunidad y se hereda vía case_id a
solicitud → cotización → operación → factura. advance_stage solo avanza.
- case_id (FK a crm.cases) en crm.opportunities/service_requests/quotes,
ops.shipments y fin.invoices; propagación en sus create_*. Migración
d4e5f6a7b8c9 reversible.
- Endpoints GET /v1/crm/cases, /cases/{id}, /cases/by-ref/{ref} con timeline
(historia completa para UI y otros sistemas).
- Frontend: casesAPI, ruta /dashboard/crm/expedientes (lista + timeline vertical),
chip "📁 Expediente" en solicitud/cotización, "Expedientes" en el sidebar.
- Consecutivo de folios sin tope (soporta >10,000,000/mes).
- 4 pruebas de expediente (minteo, propagación, timeline, no-retroceso). Suite en verde (113).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
76 lines
3.4 KiB
Python
76 lines
3.4 KiB
Python
"""Expediente (crm.cases) + case_id en el ciclo comercial
|
|
|
|
Revision ID: d4e5f6a7b8c9
|
|
Revises: f0a1b2c3d4e5
|
|
Create Date: 2026-08-07 02:00:00.000000
|
|
|
|
Crea crm.cases (expediente, hilo maestro con folio EXP...) y agrega case_id a
|
|
crm.opportunities/service_requests/quotes, ops.shipments y fin.invoices.
|
|
"""
|
|
from typing import Sequence, Union
|
|
|
|
import sqlalchemy as sa
|
|
from alembic import op
|
|
|
|
revision: str = "d4e5f6a7b8c9"
|
|
down_revision: Union[str, None] = "f0a1b2c3d4e5"
|
|
branch_labels: Union[str, Sequence[str], None] = None
|
|
depends_on: Union[str, Sequence[str], None] = None
|
|
|
|
# (schema, tabla) donde se agrega case_id
|
|
_CASE_FK_TABLES = [
|
|
("crm", "opportunities"),
|
|
("crm", "service_requests"),
|
|
("crm", "quotes"),
|
|
("ops", "shipments"),
|
|
("fin", "invoices"),
|
|
]
|
|
|
|
|
|
def upgrade() -> None:
|
|
op.create_table(
|
|
"cases",
|
|
sa.Column("id", sa.Integer(), nullable=False),
|
|
sa.Column("reference", sa.String(length=40), nullable=True),
|
|
sa.Column("account_id", sa.Integer(), nullable=True),
|
|
sa.Column("title", sa.String(length=255), nullable=True),
|
|
sa.Column("stage", sa.String(length=20), nullable=False, server_default=sa.text("'oportunidad'")),
|
|
sa.Column("status", sa.String(length=20), nullable=False, server_default=sa.text("'abierto'")),
|
|
sa.Column("created_by", sa.String(length=64), nullable=True),
|
|
sa.Column("updated_by", sa.String(length=64), nullable=True),
|
|
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),
|
|
sa.PrimaryKeyConstraint("id"),
|
|
sa.ForeignKeyConstraint(["tenant_id"], ["core.tenants.id"]),
|
|
sa.ForeignKeyConstraint(["account_id"], ["crm.accounts.id"]),
|
|
schema="crm",
|
|
)
|
|
op.create_index("ix_crm_cases_id", "cases", ["id"], schema="crm")
|
|
op.create_index("ix_crm_cases_reference", "cases", ["reference"], schema="crm")
|
|
op.create_index("ix_crm_cases_tenant_id", "cases", ["tenant_id"], schema="crm")
|
|
op.create_index("ix_crm_cases_company_id", "cases", ["company_id"], schema="crm")
|
|
op.create_index("ix_crm_cases_account_id", "cases", ["account_id"], schema="crm")
|
|
op.create_index("ix_crm_cases_status", "cases", ["status"], schema="crm")
|
|
|
|
for schema, table in _CASE_FK_TABLES:
|
|
op.add_column(table, sa.Column("case_id", sa.Integer(), nullable=True), schema=schema)
|
|
op.create_index(f"ix_{schema}_{table}_case_id", table, ["case_id"], schema=schema)
|
|
op.create_foreign_key(
|
|
f"fk_{schema}_{table}_case_id", table, "cases",
|
|
["case_id"], ["id"], source_schema=schema, referent_schema="crm",
|
|
)
|
|
|
|
|
|
def downgrade() -> None:
|
|
for schema, table in _CASE_FK_TABLES:
|
|
op.drop_constraint(f"fk_{schema}_{table}_case_id", table, schema=schema, type_="foreignkey")
|
|
op.drop_index(f"ix_{schema}_{table}_case_id", table_name=table, schema=schema)
|
|
op.drop_column(table, "case_id", schema=schema)
|
|
|
|
for idx in ("status", "account_id", "company_id", "tenant_id", "reference", "id"):
|
|
op.drop_index(f"ix_crm_cases_{idx}", table_name="cases", schema="crm")
|
|
op.drop_table("cases", schema="crm")
|