refactor: update alembic migrations and import models

- Added import for `alta_log_models` to `env.py` for autogeneration of tables outside of `models.py`.
- Deleted several obsolete migration files, including those related to classification descriptions, initial data seeding, and legacy fields in the company certification schema.
- Cleaned up migration history by removing unused migration scripts to streamline future updates.
This commit is contained in:
2026-05-01 22:10:21 -05:00
parent a5ef8f1fd1
commit 4c0fa607dc
21 changed files with 627 additions and 2002 deletions

View File

@@ -300,6 +300,9 @@ def import_models_from_dir(dir_path: str):
modules_dir = os.path.join(BASE_DIR, "api", "v1", "modules")
import_models_from_dir(modules_dir)
# Tablas declaradas fuera de models.py / carpeta models/ (autogenerate)
import api.v1.modules.a76.general_catalogs.doda.alta_log_models # noqa: F401
def run_migrations_offline() -> None:
"""Run migrations in 'offline' mode.

View File

@@ -1,27 +0,0 @@
"""add description to classification concept
Revision ID: 1f4ba75eaa35
Revises: f1a2b3c4d5e6
Create Date: 2026-04-21 20:55:04.259655
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '1f4ba75eaa35'
down_revision: Union[str, Sequence[str], None] = 'f1a2b3c4d5e6'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
op.add_column('classification_concepts', sa.Column('description', sa.String(length=255), nullable=True), schema='a76')
def downgrade() -> None:
"""Downgrade schema."""
op.drop_column('classification_concepts', 'description', schema='a76')

View File

@@ -1,189 +0,0 @@
"""company certification refactor + legacy ui fields
Revision ID: 6a7b8c9d0e1f
Revises: 1f4ba75eaa35
Create Date: 2026-04-23 11:10:00.000000
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = "6a7b8c9d0e1f"
down_revision: Union[str, Sequence[str], None] = "1f4ba75eaa35"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
SCHEMA = "a76"
TABLE = "company_certification"
def upgrade() -> None:
# Rename Annex 31 columns to Annex 30 names.
op.alter_column(TABLE, "annex31_certification_date", new_column_name="annex30_certification_date", schema=SCHEMA)
op.alter_column(TABLE, "annex31_certification_number", new_column_name="annex30_certification_number", schema=SCHEMA)
op.alter_column(TABLE, "annex31_modality", new_column_name="annex30_modality", schema=SCHEMA)
op.alter_column(TABLE, "annex31_company_type", new_column_name="annex30_company_type", schema=SCHEMA)
op.alter_column(TABLE, "annex31_renewal_date", new_column_name="annex30_renewal_date", schema=SCHEMA)
op.alter_column(TABLE, "annex31_final_certification_date", new_column_name="annex30_final_certification_date", schema=SCHEMA)
op.alter_column(
TABLE,
"annex30_modality",
existing_type=sa.String(length=50),
type_=sa.String(length=3),
schema=SCHEMA,
)
# Convert legacy integer dates (YYYYMMDD or 0) to DATE.
date_columns = [
"certified_company_start_date",
"certified_company_end_date",
"annex30_certification_date",
"annex30_renewal_date",
"annex30_final_certification_date",
]
for col in date_columns:
op.alter_column(
TABLE,
col,
existing_type=sa.Integer(),
type_=sa.Date(),
schema=SCHEMA,
postgresql_using=(
f"CASE "
f"WHEN {col} IS NULL OR {col} = 0 THEN NULL "
f"WHEN length({col}::text) = 8 THEN to_date({col}::text, 'YYYYMMDD') "
f"ELSE NULL END"
),
)
# Convert legacy S/N and 0/1 flags to native booleans.
op.alter_column(
TABLE,
"is_certified_company",
existing_type=sa.String(length=1),
type_=sa.Boolean(),
schema=SCHEMA,
postgresql_using=(
"CASE "
"WHEN is_certified_company IS NULL THEN NULL "
"WHEN upper(trim(is_certified_company)) IN ('S','SI','1','T','TRUE','Y','YES') THEN true "
"WHEN upper(trim(is_certified_company)) IN ('N','NO','0','F','FALSE') THEN false "
"ELSE NULL END"
),
)
op.alter_column(
TABLE,
"is_oea_company",
existing_type=sa.SmallInteger(),
type_=sa.Boolean(),
schema=SCHEMA,
postgresql_using="CASE WHEN is_oea_company IS NULL THEN NULL WHEN is_oea_company = 1 THEN true ELSE false END",
)
op.alter_column(
TABLE,
"neec_company",
existing_type=sa.Integer(),
type_=sa.Boolean(),
schema=SCHEMA,
postgresql_using="CASE WHEN neec_company IS NULL THEN NULL WHEN neec_company = 1 THEN true ELSE false END",
)
# Additional legacy UI fields.
op.add_column(
"company",
sa.Column("fiscal_deposit", sa.Boolean(), nullable=True, server_default=sa.text("false")),
schema=SCHEMA,
)
op.add_column(
"company",
sa.Column(
"generate_barcodes_with_fiel",
sa.Boolean(),
nullable=True,
server_default=sa.text("false"),
),
schema=SCHEMA,
)
op.add_column(
"company_certification",
sa.Column("is_seciit_company", sa.Boolean(), nullable=True, server_default=sa.text("false")),
schema=SCHEMA,
)
def downgrade() -> None:
# Remove additional legacy UI fields.
op.drop_column("company_certification", "is_seciit_company", schema=SCHEMA)
op.drop_column("company", "generate_barcodes_with_fiel", schema=SCHEMA)
op.drop_column("company", "fiscal_deposit", schema=SCHEMA)
# Restore booleans to legacy flag formats.
op.alter_column(
TABLE,
"is_certified_company",
existing_type=sa.Boolean(),
type_=sa.String(length=1),
schema=SCHEMA,
postgresql_using="CASE WHEN is_certified_company IS NULL THEN NULL WHEN is_certified_company THEN 'S' ELSE 'N' END",
)
op.alter_column(
TABLE,
"is_oea_company",
existing_type=sa.Boolean(),
type_=sa.SmallInteger(),
schema=SCHEMA,
postgresql_using="CASE WHEN is_oea_company IS NULL THEN NULL WHEN is_oea_company THEN 1 ELSE 0 END",
)
op.alter_column(
TABLE,
"neec_company",
existing_type=sa.Boolean(),
type_=sa.Integer(),
schema=SCHEMA,
postgresql_using="CASE WHEN neec_company IS NULL THEN NULL WHEN neec_company THEN 1 ELSE 0 END",
)
# Restore DATE columns to integer YYYYMMDD format.
date_columns = [
"certified_company_start_date",
"certified_company_end_date",
"annex30_certification_date",
"annex30_renewal_date",
"annex30_final_certification_date",
]
for col in date_columns:
op.alter_column(
TABLE,
col,
existing_type=sa.Date(),
type_=sa.Integer(),
schema=SCHEMA,
postgresql_using=f"CASE WHEN {col} IS NULL THEN NULL ELSE to_char({col}, 'YYYYMMDD')::integer END",
)
op.alter_column(
TABLE,
"annex30_modality",
existing_type=sa.String(length=3),
type_=sa.String(length=50),
schema=SCHEMA,
)
# Rename Annex 30 columns back to Annex 31.
op.alter_column(TABLE, "annex30_certification_date", new_column_name="annex31_certification_date", schema=SCHEMA)
op.alter_column(TABLE, "annex30_certification_number", new_column_name="annex31_certification_number", schema=SCHEMA)
op.alter_column(TABLE, "annex30_modality", new_column_name="annex31_modality", schema=SCHEMA)
op.alter_column(TABLE, "annex30_company_type", new_column_name="annex31_company_type", schema=SCHEMA)
op.alter_column(TABLE, "annex30_renewal_date", new_column_name="annex31_renewal_date", schema=SCHEMA)
op.alter_column(TABLE, "annex30_final_certification_date", new_column_name="annex31_final_certification_date", schema=SCHEMA)

View File

@@ -1,12 +1,10 @@
"""seed_initial_data
Revision ID: 7937209f9718
Revises:
Create Date: 2025-10-19 18:23:55.258800
Revision ID: 8c9bad3da37f
Revises: 9db46c604463
Create Date: 2026-05-01 22:01:50.174319
"""
# pylint: disable=no-member
from typing import Sequence, Union
@@ -87,44 +85,21 @@ from api.v1.modules.public.reference_data.carta_porte_codes.seed import seed_car
from sqlalchemy.orm import Session
# revision identifiers, used by Alembic.
revision: str = "7937209f9718"
down_revision: Union[str, Sequence[str], None] = "4ad64605fad2"
revision: str = "8c9bad3da37f"
down_revision: Union[str, Sequence[str], None] = "9db46c604463"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = "4ad64605fad2"
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
"""Catálogos de referencia y permisos base (datos)."""
def format_value(val):
if val is None or str(val).strip() == "" or str(val).upper() == "NONE":
return "NULL"
return f"'{str(val).replace(chr(39), chr(39)*2)}'"
op.execute(
"""
CREATE TABLE IF NOT EXISTS public.pedimento_transport_catalog (
code VARCHAR(3) NOT NULL,
transport_en VARCHAR(80) NOT NULL,
transport_es VARCHAR(120) NOT NULL,
payment_date_code VARCHAR(1) NOT NULL,
CONSTRAINT pedimento_transport_catalog_pkey PRIMARY KEY (code),
CONSTRAINT pedimento_transport_catalog_payment_date_code_chk
CHECK (payment_date_code IN ('E','P'))
);
"""
)
op.execute(
"""
ALTER TABLE IF EXISTS a76.pedimento_transport_means
ALTER COLUMN entry_exit TYPE VARCHAR(3),
ALTER COLUMN arrival TYPE VARCHAR(3),
ALTER COLUMN departure TYPE VARCHAR(3);
"""
)
# --- SEEDS PUBLIC (Tablas base) ---
# Seeds
values_pc = ", ".join(
[
f"('{code}', '{desc.replace(chr(39), chr(39)*2)}')"
@@ -364,8 +339,6 @@ def upgrade() -> None:
)
# --- SEEDS A76 (Unidades de Medida) ---
# ACE
val_ace = ", ".join(
[f"({format_value(c)}, {format_value(d)})" for c, d in ace_seed]
)
@@ -373,7 +346,6 @@ def upgrade() -> None:
f"INSERT INTO a76.unit_of_measure_ace (code, description) VALUES {val_ace} ON CONFLICT ON CONSTRAINT uq_uom_ace_code DO NOTHING;"
)
# OMA
val_oma = ", ".join(
[f"({format_value(c)}, {format_value(d)})" for c, d in oma_seed]
)
@@ -381,7 +353,6 @@ def upgrade() -> None:
f"INSERT INTO a76.unit_of_measure_oma (code, description) VALUES {val_oma} ON CONFLICT ON CONSTRAINT uq_uom_oma_code DO NOTHING;"
)
# AME
val_ame = ", ".join(
[f"({format_value(c)}, {format_value(d)})" for c, d in ame_seed]
)
@@ -389,7 +360,6 @@ def upgrade() -> None:
f"INSERT INTO a76.unit_of_measure_american (code, description) VALUES {val_ame} ON CONFLICT ON CONSTRAINT uq_uom_american_code DO NOTHING;"
)
# ADUA (Customs) - seed_adua: (code, description, a76_unit_code)
val_adua = ", ".join(
[f"({format_value(code)}, {format_value(desc)}, {format_value(a76_code)})" for code, desc, a76_code in adua_seed]
)
@@ -397,7 +367,6 @@ def upgrade() -> None:
f"INSERT INTO a76.unit_of_measure_customs (code, description, a76_unit_code) VALUES {val_adua} ON CONFLICT ON CONSTRAINT uq_uom_customs_code DO NOTHING;"
)
# Recolectar códigos adicionales que faltan en los catálogos
additional_customs = set()
additional_american = set()
additional_ace = set()
@@ -418,10 +387,9 @@ def upgrade() -> None:
if oma and oma.strip() and oma not in existing_oma:
additional_oma.add((oma, f"Auto-generated from {code}"))
# Insertar códigos adicionales
if additional_customs:
val_add_customs = ", ".join(
[f"({format_value(c)}, {format_value(d)}, {format_value(s)})" for c, d, s in additional_customs]
[f"({format_value(c)}, {format_value(d)}, NULL)" for c, d in additional_customs]
)
op.execute(
f"INSERT INTO a76.unit_of_measure_customs (code, description, a76_unit_code) VALUES {val_add_customs} ON CONFLICT ON CONSTRAINT uq_uom_customs_code DO NOTHING;"
@@ -451,11 +419,7 @@ def upgrade() -> None:
f"INSERT INTO a76.unit_of_measure_oma (code, description) VALUES {val_add_oma} ON CONFLICT ON CONSTRAINT uq_uom_oma_code DO NOTHING;"
)
# TABLA MAESTRA UOM se genera ahora al crear una empresa
# --- SEEDS CORE (Permissions) ---
# Combinar todas las seeds de permisos desde el Registro V2
all_permissions = registry.get_all()
values_permissions = ", ".join(
@@ -474,7 +438,6 @@ def upgrade() -> None:
"""
)
# --- SEEDS PUBLIC (States) ---
values_states = ", ".join(
[
f"('{m3_key}', '{description.replace(chr(39), chr(39)*2)}', {format_value(mex_key)})"
@@ -489,7 +452,6 @@ def upgrade() -> None:
"""
)
# --- SEEDS A76 (Tariff Fractions - Fracciones Arancelarias Mexicanas) ---
values_tariff_fractions = ", ".join(
[
f"({format_value(code)}, {format_value(fraction)}, {format_value(description)}, "
@@ -505,9 +467,6 @@ def upgrade() -> None:
"""
)
# Historical Fractions se generan ahora al crear una empresa
# --- SEEDS NEW REFERENCE CATALOGS ---
bind = op.get_bind()
session = Session(bind=bind)
seed_license_exceptions(session)
@@ -517,44 +476,5 @@ def upgrade() -> None:
def downgrade() -> None:
"""Downgrade schema."""
op.execute(
"""
ALTER TABLE IF EXISTS a76.pedimento_transport_means
ALTER COLUMN entry_exit TYPE VARCHAR(2),
ALTER COLUMN arrival TYPE VARCHAR(2),
ALTER COLUMN departure TYPE VARCHAR(2);
"""
)
op.drop_table("pedimento_transport_catalog", schema="public")
op.drop_table("us_tariff_fractions", schema="a76")
op.drop_table("historical_tariff_fractions", schema="a76")
op.drop_table("canadian_tariff_fractions", schema="a76")
op.drop_table("valuation_methods", schema="public")
op.drop_table("transport_types", schema="public")
op.drop_table("trailer_types", schema="public")
op.drop_table("transport_modes", schema="public")
op.drop_table("payment_methods", schema="public")
op.drop_table("material_types", schema="public")
op.drop_table("invoice_types", schema="public")
op.drop_table("incoterms", schema="public")
op.drop_table("customs_warehouses", schema="public")
op.drop_table("customs_sections", schema="public")
op.drop_table("currency_types", schema="public")
op.drop_table("countries", schema="public")
op.drop_table("containers", schema="public")
op.drop_table("code_pedimento_regimens", schema="public")
op.drop_table("pedimento_regimens", schema="public")
op.drop_table("pedimento_codes", schema="public")
op.drop_table("user_client_permissions", schema="core")
op.drop_table("user_client_roles", schema="core")
op.drop_table("role_permissions", schema="core")
op.drop_table("client_roles", schema="core")
op.drop_table("permissions", schema="core")
op.drop_table("units_of_measure", schema="a76")
op.drop_table("unit_of_measure_customs", schema="a76")
op.drop_table("unit_of_measure_american", schema="a76")
op.drop_table("unit_of_measure_oma", schema="a76")
op.drop_table("unit_of_measure_ace", schema="a76")
"""Los datos de catálogo no se revierten automáticamente."""
pass

View File

@@ -1,45 +0,0 @@
"""drop_legacy_return_counters
Revision ID: 9f3c2d1b7a11
Revises: 4ad64605fad2
Create Date: 2026-03-20 15:10:00.000000
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = "9f3c2d1b7a11"
down_revision: Union[str, Sequence[str], None] = "7937209f9718"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Drop legacy quantity counters replaced by balance/discharge ledger."""
op.drop_column("item_line_quantities", "quantity_returned_temp", schema="a76")
op.drop_column("item_line_quantities", "quantity_returned", schema="a76")
op.drop_column("item_line_quantities", "quantity_existence", schema="a76")
def downgrade() -> None:
"""Restore legacy quantity counters."""
op.add_column(
"item_line_quantities",
sa.Column("quantity_existence", sa.Numeric(precision=19, scale=8), nullable=True),
schema="a76",
)
op.add_column(
"item_line_quantities",
sa.Column("quantity_returned", sa.Numeric(precision=19, scale=8), nullable=True),
schema="a76",
)
op.add_column(
"item_line_quantities",
sa.Column("quantity_returned_temp", sa.Numeric(precision=19, scale=8), nullable=True),
schema="a76",
)

View File

@@ -1,117 +0,0 @@
"""create doda_alta_log table
Revision ID: a1b2c3d4e5f6
Revises: d1a2b3c4e5f6
Create Date: 2026-04-26 10:00:00.000000
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = "a1b2c3d4e5f6"
down_revision = "d1a2b3c4e5f6"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.create_table(
"doda_alta_log",
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
sa.Column("doda_id", sa.Integer(), nullable=True),
sa.Column("variant", sa.String(length=10), nullable=True),
sa.Column("responsible", sa.String(length=20), nullable=True),
sa.Column("patent", sa.String(length=10), nullable=True),
sa.Column("dispatch_customs", sa.String(length=10), nullable=True),
sa.Column("operation_type", sa.String(length=5), nullable=True),
sa.Column("integration_number", sa.String(length=50), nullable=True),
sa.Column("task_id", sa.String(length=255), nullable=True),
sa.Column("status", sa.String(length=30), nullable=True),
sa.Column("message", sa.String(length=2000), nullable=True),
sa.Column("result_json", sa.Text(), nullable=True),
sa.Column("tenant_id", sa.Integer(), nullable=False),
sa.Column("company_id", sa.Integer(), nullable=False),
sa.Column("created_at", sa.DateTime(), server_default=sa.text("now()"), nullable=False),
sa.Column("updated_at", sa.DateTime(), server_default=sa.text("now()"), nullable=False),
sa.Column("deleted_at", sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(["company_id"], ["a76.company.id"]),
sa.ForeignKeyConstraint(["tenant_id"], ["core.tenants.id"]),
sa.PrimaryKeyConstraint("id", name="doda_alta_log_pkey"),
schema="a76",
)
op.create_index(
op.f("ix_a76_doda_alta_log_company_id"),
"doda_alta_log",
["company_id"],
unique=False,
schema="a76",
)
op.create_index(
op.f("ix_a76_doda_alta_log_tenant_id"),
"doda_alta_log",
["tenant_id"],
unique=False,
schema="a76",
)
op.create_index(
op.f("ix_a76_doda_alta_log_doda_id"),
"doda_alta_log",
["doda_id"],
unique=False,
schema="a76",
)
op.create_index(
op.f("ix_a76_doda_alta_log_task_id"),
"doda_alta_log",
["task_id"],
unique=False,
schema="a76",
)
# Reporte PDF almacenado (S3) + invalidación por huella de contenido
op.add_column(
"doda",
sa.Column("doda_report_pdf_path", sa.String(length=1000), nullable=True),
schema="a76",
)
op.add_column(
"doda",
sa.Column("doda_report_pdf_generated_at", sa.DateTime(), nullable=True),
schema="a76",
)
op.add_column(
"doda",
sa.Column("doda_report_source_fingerprint", sa.String(length=64), nullable=True),
schema="a76",
)
def downgrade() -> None:
op.drop_column("doda", "doda_report_source_fingerprint", schema="a76")
op.drop_column("doda", "doda_report_pdf_generated_at", schema="a76")
op.drop_column("doda", "doda_report_pdf_path", schema="a76")
op.drop_index(
op.f("ix_a76_doda_alta_log_task_id"),
table_name="doda_alta_log",
schema="a76",
)
op.drop_index(
op.f("ix_a76_doda_alta_log_doda_id"),
table_name="doda_alta_log",
schema="a76",
)
op.drop_index(
op.f("ix_a76_doda_alta_log_tenant_id"),
table_name="doda_alta_log",
schema="a76",
)
op.drop_index(
op.f("ix_a76_doda_alta_log_company_id"),
table_name="doda_alta_log",
schema="a76",
)
op.drop_table("doda_alta_log", schema="a76")

View File

@@ -1,28 +0,0 @@
"""drop client_id column from parts
Revision ID: b2c3d4e5f6a7
Revises: a1b2c3d4e5f6
Create Date: 2026-04-28 11:50:00.000000
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = "b2c3d4e5f6a7"
down_revision = "a1b2c3d4e5f6"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.drop_column("parts", "client_id", schema="a76")
def downgrade() -> None:
op.add_column(
"parts",
sa.Column("client_id", sa.Integer(), nullable=True),
schema="a76",
)

View File

@@ -1,45 +0,0 @@
"""iva_factor
Revision ID: bccb7f8986c7
Revises: 9f3c2d1b7a11
Create Date: 2026-03-23 09:44:02.275257
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'bccb7f8986c7'
down_revision: Union[str, Sequence[str], None] = '9f3c2d1b7a11'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_public_carta_porte_code'), table_name='carta_porte_codes')
op.create_index(op.f('ix_public_carta_porte_codes_code'), 'carta_porte_codes', ['code'], unique=False, schema='public')
op.alter_column('invoice_financials', 'iva_factor',
existing_type=sa.VARCHAR(length=10),
type_=sa.Numeric(precision=23, scale=8),
postgresql_using='iva_factor::numeric(23,8)',
existing_nullable=True,
schema='a76')
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('invoice_financials', 'iva_factor',
existing_type=sa.Numeric(precision=23, scale=8),
type_=sa.VARCHAR(length=10),
existing_nullable=True,
schema='a76')
op.drop_index(op.f('ix_public_carta_porte_codes_code'), table_name='carta_porte_codes', schema='public')
op.create_index(op.f('ix_public_carta_porte_code'), 'carta_porte_codes', ['code'], unique=False)
# ### end Alembic commands ###

View File

@@ -1,142 +0,0 @@
"""create_core_task_runs and backfill code_pedimento_regimens
Revision ID: c1a2b3d4e5f6
Revises: bccb7f8986c7
Create Date: 2026-03-24 11:30:00.000000
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
from api.v1.modules.public.reference_data.code_pedimento_regimens.seed import (
seed as code_pedimento_regimens_seed_full,
)
from api.v1.modules.public.reference_data.pedimento_codes.seed import (
seed as pedimento_codes_seed,
)
# revision identifiers, used by Alembic.
revision: str = "c1a2b3d4e5f6"
down_revision: Union[str, Sequence[str], None] = "bccb7f8986c7"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def _format_value(val: str | None) -> str:
if val is None or str(val).strip() == "" or str(val).upper() == "NONE":
return "NULL"
return f"'{str(val).replace(chr(39), chr(39) * 2)}'"
def upgrade() -> None:
op.create_table(
"task_runs",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("task_id", sa.String(length=255), nullable=False),
sa.Column("tenant_id", sa.Integer(), nullable=False),
sa.Column("company_id", sa.Integer(), nullable=True),
sa.Column("requested_by_user", sa.String(length=255), nullable=True),
sa.Column("task_name", sa.String(length=255), nullable=False),
sa.Column("task_group", sa.String(length=100), nullable=False),
sa.Column("task_origin", sa.String(length=255), nullable=True),
sa.Column("status", sa.String(length=20), nullable=False),
sa.Column("celery_state_raw", sa.String(length=30), nullable=False),
sa.Column("progress_current", sa.Integer(), nullable=True),
sa.Column("progress_total", sa.Integer(), nullable=True),
sa.Column("progress_percent", sa.Float(), nullable=True),
sa.Column("progress_message", sa.String(length=500), nullable=True),
sa.Column("retries", sa.Integer(), nullable=True),
sa.Column("exception_type", sa.String(length=255), nullable=True),
sa.Column("exception_message", sa.Text(), nullable=True),
sa.Column("traceback_excerpt", sa.Text(), nullable=True),
sa.Column("result_summary", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column("meta_payload", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column("started_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("finished_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
sa.ForeignKeyConstraint(["company_id"], ["a76.company.id"]),
sa.ForeignKeyConstraint(["tenant_id"], ["core.tenants.id"]),
sa.PrimaryKeyConstraint("id"),
schema="core",
)
op.create_index("ix_core_task_runs_task_id", "task_runs", ["task_id"], unique=True, schema="core")
op.create_index("ix_core_task_runs_tenant_updated", "task_runs", ["tenant_id", "updated_at"], unique=False, schema="core")
op.create_index(
"ix_core_task_runs_tenant_status_updated",
"task_runs",
["tenant_id", "status", "updated_at"],
unique=False,
schema="core",
)
op.create_index(
"ix_core_task_runs_tenant_group_updated",
"task_runs",
["tenant_id", "task_group", "updated_at"],
unique=False,
schema="core",
)
op.create_index(
"ix_core_task_runs_tenant_company_updated",
"task_runs",
["tenant_id", "company_id", "updated_at"],
unique=False,
schema="core",
)
pedimento_desc_by_code = {code: desc for code, desc in pedimento_codes_seed}
required_pedimento_codes = sorted(
{pedimento_code for pedimento_code, _regimen_code, _type_code in code_pedimento_regimens_seed_full}
)
values_missing_codes = ", ".join(
[
f"({_format_value(code)}, {_format_value(pedimento_desc_by_code.get(code, f'AUTO-GENERATED FOR FK ({code})'))})"
for code in required_pedimento_codes
]
)
op.execute(
f"""
INSERT INTO public.pedimento_codes (code, description)
VALUES {values_missing_codes}
ON CONFLICT (code) DO NOTHING;
"""
)
values_relations = ", ".join(
[
f"({_format_value(pedimento_code)}, {_format_value(regimen_code)}, {_format_value(type_code)})"
for pedimento_code, regimen_code, type_code in code_pedimento_regimens_seed_full
]
)
op.execute(
f"""
INSERT INTO public.code_pedimento_regimens (pedimento_code, regimen_code, type_code)
SELECT src.pedimento_code, src.regimen_code, src.type_code
FROM (VALUES {values_relations}) AS src(pedimento_code, regimen_code, type_code)
JOIN public.pedimento_codes pc
ON pc.code = src.pedimento_code
JOIN public.pedimento_regimens pr
ON pr.code = src.regimen_code
WHERE NOT EXISTS (
SELECT 1
FROM public.code_pedimento_regimens cpr
WHERE cpr.pedimento_code = src.pedimento_code
AND cpr.regimen_code = src.regimen_code
AND COALESCE(cpr.type_code, '') = COALESCE(src.type_code, '')
);
"""
)
def downgrade() -> None:
op.drop_index("ix_core_task_runs_tenant_company_updated", table_name="task_runs", schema="core")
op.drop_index("ix_core_task_runs_tenant_group_updated", table_name="task_runs", schema="core")
op.drop_index("ix_core_task_runs_tenant_status_updated", table_name="task_runs", schema="core")
op.drop_index("ix_core_task_runs_tenant_updated", table_name="task_runs", schema="core")
op.drop_index("ix_core_task_runs_task_id", table_name="task_runs", schema="core")
op.drop_table("task_runs", schema="core")

View File

@@ -1,29 +0,0 @@
"""add action column to doda_alta_log
Revision ID: c3d4e5f6a7b8
Revises: b2c3d4e5f6a7
Create Date: 2026-04-28 13:20:00.000000
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = "c3d4e5f6a7b8"
down_revision = "b2c3d4e5f6a7"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.add_column(
"doda_alta_log",
sa.Column("action", sa.String(length=20), nullable=True),
schema="a76",
)
def downgrade() -> None:
op.drop_column("doda_alta_log", "action", schema="a76")

View File

@@ -1,49 +0,0 @@
"""Add has_express_line to transporter; remove from company.
Revision ID: c8d9e0f1a2b3
Revises: 6a7b8c9d0e1f
Create Date: 2026-04-24
Upgrade: add transporter column first (NOT NULL + default), then drop company column.
Downgrade: restore company column, drop transporter column (schema only; data not restored).
"""
from typing import Sequence, Union
import sqlalchemy as sa
from alembic import op
revision: str = "c8d9e0f1a2b3"
down_revision: Union[str, Sequence[str], None] = "6a7b8c9d0e1f"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
SCHEMA = "a76"
def upgrade() -> None:
op.add_column(
"transporter",
sa.Column(
"has_express_line",
sa.Boolean(),
server_default=sa.text("false"),
nullable=False,
),
schema=SCHEMA,
)
op.drop_column("company", "has_express_line", schema=SCHEMA)
def downgrade() -> None:
op.add_column(
"company",
sa.Column(
"has_express_line",
sa.Boolean(),
server_default=sa.text("false"),
nullable=True,
),
schema=SCHEMA,
)
op.drop_column("transporter", "has_express_line", schema=SCHEMA)

View File

@@ -1,266 +0,0 @@
"""Add surrogate int IDs for transport catalogs and invoice logistics.
Single consolidated migration for:
- transporter_id, trailer_id, vehicle_id, driver_id
- carrier_int_id, transport_int_id, trailer_int_id in invoice_logistics
- backfill mappings from existing string keys/codes
- FK constraints for the new invoice_logistics int references
"""
from __future__ import annotations
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
revision: str = "ca7d3c4e8b2a"
down_revision: Union[str, Sequence[str], None] = "c1a2b3d4e5f6"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# transporter
op.add_column("transporter", sa.Column("transporter_id", sa.BigInteger(), nullable=True), schema="a76")
op.execute(
"""
WITH s AS (
SELECT transporter_key, ROW_NUMBER() OVER (ORDER BY transporter_key) AS new_id
FROM a76.transporter
)
UPDATE a76.transporter t
SET transporter_id = s.new_id
FROM s
WHERE t.transporter_key = s.transporter_key
"""
)
op.execute("CREATE SEQUENCE IF NOT EXISTS a76.transporter_transporter_id_seq")
op.execute(
"""
SELECT setval(
'a76.transporter_transporter_id_seq',
COALESCE((SELECT MAX(transporter_id) FROM a76.transporter), 0) + 1,
false
)
"""
)
op.execute(
"""
ALTER TABLE a76.transporter
ALTER COLUMN transporter_id
SET DEFAULT nextval('a76.transporter_transporter_id_seq')
"""
)
op.execute("ALTER TABLE a76.transporter ALTER COLUMN transporter_id SET NOT NULL")
op.create_unique_constraint("uq_a76_transporter_transporter_id", "transporter", ["transporter_id"], schema="a76")
# trailer
op.add_column("trailer", sa.Column("trailer_id", sa.BigInteger(), nullable=True), schema="a76")
op.execute(
"""
WITH s AS (
SELECT trailer_number, ROW_NUMBER() OVER (ORDER BY trailer_number) AS new_id
FROM a76.trailer
)
UPDATE a76.trailer t
SET trailer_id = s.new_id
FROM s
WHERE t.trailer_number = s.trailer_number
"""
)
op.execute("CREATE SEQUENCE IF NOT EXISTS a76.trailer_trailer_id_seq")
op.execute(
"""
SELECT setval(
'a76.trailer_trailer_id_seq',
COALESCE((SELECT MAX(trailer_id) FROM a76.trailer), 0) + 1,
false
)
"""
)
op.execute(
"""
ALTER TABLE a76.trailer
ALTER COLUMN trailer_id
SET DEFAULT nextval('a76.trailer_trailer_id_seq')
"""
)
op.execute("ALTER TABLE a76.trailer ALTER COLUMN trailer_id SET NOT NULL")
op.create_unique_constraint("uq_a76_trailer_trailer_id", "trailer", ["trailer_id"], schema="a76")
# vehicle
op.add_column("vehicle", sa.Column("vehicle_id", sa.BigInteger(), nullable=True), schema="a76")
op.execute(
"""
WITH s AS (
SELECT vehicle_key, ROW_NUMBER() OVER (ORDER BY vehicle_key) AS new_id
FROM a76.vehicle
)
UPDATE a76.vehicle v
SET vehicle_id = s.new_id
FROM s
WHERE v.vehicle_key = s.vehicle_key
"""
)
op.execute("CREATE SEQUENCE IF NOT EXISTS a76.vehicle_vehicle_id_seq")
op.execute(
"""
SELECT setval(
'a76.vehicle_vehicle_id_seq',
COALESCE((SELECT MAX(vehicle_id) FROM a76.vehicle), 0) + 1,
false
)
"""
)
op.execute(
"""
ALTER TABLE a76.vehicle
ALTER COLUMN vehicle_id
SET DEFAULT nextval('a76.vehicle_vehicle_id_seq')
"""
)
op.execute("ALTER TABLE a76.vehicle ALTER COLUMN vehicle_id SET NOT NULL")
op.create_unique_constraint("uq_a76_vehicle_vehicle_id", "vehicle", ["vehicle_id"], schema="a76")
# driver
op.add_column("driver", sa.Column("driver_id", sa.BigInteger(), nullable=True), schema="a76")
op.execute(
"""
WITH s AS (
SELECT transporter_key, line, ROW_NUMBER() OVER (ORDER BY transporter_key, line) AS new_id
FROM a76.driver
)
UPDATE a76.driver d
SET driver_id = s.new_id
FROM s
WHERE d.transporter_key = s.transporter_key
AND d.line = s.line
"""
)
op.execute("CREATE SEQUENCE IF NOT EXISTS a76.driver_driver_id_seq")
op.execute(
"""
SELECT setval(
'a76.driver_driver_id_seq',
COALESCE((SELECT MAX(driver_id) FROM a76.driver), 0) + 1,
false
)
"""
)
op.execute(
"""
ALTER TABLE a76.driver
ALTER COLUMN driver_id
SET DEFAULT nextval('a76.driver_driver_id_seq')
"""
)
op.execute("ALTER TABLE a76.driver ALTER COLUMN driver_id SET NOT NULL")
op.create_unique_constraint("uq_a76_driver_driver_id", "driver", ["driver_id"], schema="a76")
# invoice_logistics int refs
op.add_column("invoice_logistics", sa.Column("carrier_int_id", sa.BigInteger(), nullable=True), schema="a76")
op.add_column("invoice_logistics", sa.Column("transport_int_id", sa.BigInteger(), nullable=True), schema="a76")
op.add_column("invoice_logistics", sa.Column("trailer_int_id", sa.BigInteger(), nullable=True), schema="a76")
# ensure referenced codes exist
op.execute(
"""
INSERT INTO a76.transporter (transporter_key, tenant_id, company_id)
SELECT DISTINCT ON (il.carrier_id) il.carrier_id, il.tenant_id, il.company_id
FROM a76.invoice_logistics il
WHERE il.carrier_id IS NOT NULL
AND NOT EXISTS (
SELECT 1 FROM a76.transporter t WHERE t.transporter_key = il.carrier_id
)
"""
)
op.execute(
"""
INSERT INTO a76.vehicle (vehicle_key, tenant_id, company_id)
SELECT DISTINCT ON (il.transport_id) il.transport_id, il.tenant_id, il.company_id
FROM a76.invoice_logistics il
WHERE il.transport_id IS NOT NULL
AND NOT EXISTS (
SELECT 1 FROM a76.vehicle v WHERE v.vehicle_key = il.transport_id
)
"""
)
op.execute(
"""
INSERT INTO a76.trailer (trailer_number, tenant_id, company_id)
SELECT DISTINCT ON (il.trailer_num) il.trailer_num, il.tenant_id, il.company_id
FROM a76.invoice_logistics il
WHERE il.trailer_num IS NOT NULL
AND NOT EXISTS (
SELECT 1 FROM a76.trailer tr WHERE tr.trailer_number = il.trailer_num
)
"""
)
# backfill int refs
op.execute(
"""
UPDATE a76.invoice_logistics il
SET carrier_int_id = t.transporter_id
FROM a76.transporter t
WHERE il.carrier_id IS NOT NULL
AND il.carrier_id = t.transporter_key
"""
)
op.execute(
"""
UPDATE a76.invoice_logistics il
SET transport_int_id = v.vehicle_id
FROM a76.vehicle v
WHERE il.transport_id IS NOT NULL
AND il.transport_id = v.vehicle_key
"""
)
op.execute(
"""
UPDATE a76.invoice_logistics il
SET trailer_int_id = tr.trailer_id
FROM a76.trailer tr
WHERE il.trailer_num IS NOT NULL
AND il.trailer_num = tr.trailer_number
"""
)
# FK constraints
op.create_foreign_key(
"fk_a76_invoice_logistics_carrier_int_id_transporter_id",
"invoice_logistics",
"transporter",
["carrier_int_id"],
["transporter_id"],
source_schema="a76",
referent_schema="a76",
ondelete="SET NULL",
)
op.create_foreign_key(
"fk_a76_invoice_logistics_transport_int_id_vehicle_id",
"invoice_logistics",
"vehicle",
["transport_int_id"],
["vehicle_id"],
source_schema="a76",
referent_schema="a76",
ondelete="SET NULL",
)
op.create_foreign_key(
"fk_a76_invoice_logistics_trailer_int_id_trailer_id",
"invoice_logistics",
"trailer",
["trailer_int_id"],
["trailer_id"],
source_schema="a76",
referent_schema="a76",
ondelete="SET NULL",
)
def downgrade() -> None:
pass

View File

@@ -1,302 +0,0 @@
"""enable_rls_tenant_company
Habilita Row-Level Security en las tablas multi-tenant conforme al skill
`aduanasoft-dev-standards` (sección 10). Las políticas dependen de dos
GUCs que la aplicación establece por transacción con `SET LOCAL`:
- ``app.tenant_id`` (ID del tenant actual, obligatorio para aislamiento)
- ``app.company_id`` (ID de la compañía activa; opcional — si no está fijado
la política permite todas las compañías del tenant, útil para vistas de
selector de compañía / bootstrap de sesión)
Las funciones SQL viven en el esquema ``app`` y retornan ``NULL`` cuando la
GUC correspondiente está vacía, lo que hace que las comparaciones
``col = app.current_xxx_id()`` devuelvan 0 filas sin contexto (fail-closed
para ``tenant_id``).
Las tablas ``core.tenants`` y ``core.user_tenants`` NO quedan bajo RLS: son
necesarias para el bootstrap de la sesión (obtener tenant del JWT y listar
los tenants del usuario en el selector).
La migración instala ``FORCE ROW LEVEL SECURITY`` para que las políticas
apliquen también al owner — los superusuarios (p. ej. ``postgres`` en dev)
siguen haciendo bypass por diseño de PostgreSQL; en producción la API debe
conectarse con un rol sin BYPASSRLS.
Revision ID: d1a2b3c4e5f6
Revises: c8d9e0f1a2b3
Create Date: 2026-04-24 17:00:00.000000
"""
from typing import Sequence, Union
from alembic import op
revision: str = "d1a2b3c4e5f6"
down_revision: Union[str, Sequence[str], None] = "c8d9e0f1a2b3"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
TABLES_TENANT_ONLY: list[tuple[str, str]] = [
("a76", "company"),
("core", "license_usage"),
("core", "licenses"),
]
TABLES_TENANT_AND_COMPANY: list[tuple[str, str]] = [
("a24", "balance_movement"),
("a24", "discharge_detail"),
("a24", "discharge_header"),
("a24", "discharge_scrap"),
("a24", "fa_classes"),
("a24", "fa_item_lines"),
("a24", "fa_partes"),
("a24", "inv_aphis_characteristic"),
("a24", "inv_aphis_containers"),
("a24", "inv_aphis_entities"),
("a24", "inv_aphis_general"),
("a24", "inv_aphis_lpcos"),
("a24", "inv_aphis_routing"),
("a24", "inv_aphis_stype_pitems"),
("a24", "inv_bom"),
("a24", "inv_classes"),
("a24", "inv_parte_paises"),
("a24", "inv_partes"),
("a76", "app_settings"),
("a76", "audit_logs"),
("a76", "canadian_tariff_fractions"),
("a76", "classes"),
("a76", "classification_concepts"),
("a76", "clients_and_providers"),
("a76", "clients_and_providers_address"),
("a76", "clients_and_providers_programs"),
("a76", "concept_manifestations"),
("a76", "concepts"),
("a76", "country_rule_oct"),
("a76", "ctm_receipts"),
("a76", "customs_broker_concepts"),
("a76", "customs_brokers"),
("a76", "customs_brokers_personnel"),
("a76", "customs_brokers_vu"),
("a76", "depreciation_catalog"),
("a76", "document_types_digitization"),
("a76", "doda"),
("a76", "doda_american_pedimentos"),
("a76", "doda_container_seals"),
("a76", "doda_containers"),
("a76", "doda_pedimentos"),
("a76", "driver"),
("a76", "electronic_notices"),
("a76", "equivalencies"),
("a76", "equivalency_items"),
("a76", "error_catalogs"),
("a76", "error_classifications"),
("a76", "exchange_rate"),
("a76", "fa_location_ext"),
("a76", "fda_affirmation_codes"),
("a76", "fda_catalog"),
("a76", "fda_constituent_elements"),
("a76", "fda_lot_production"),
("a76", "fda_specifications"),
("a76", "fraction_rule_octave"),
("a76", "historical_tariff_fractions"),
("a76", "identifier_details"),
("a76", "identifiers"),
("a76", "inpc"),
("a76", "invoice_collections"),
("a76", "invoice_compliance_mx"),
("a76", "invoice_financials"),
("a76", "invoice_header"),
("a76", "invoice_logistics"),
("a76", "invoice_sales_details"),
("a76", "invoice_settings"),
("a76", "item_line_series"),
("a76", "item_lines"),
("a76", "item_presets"),
("a76", "legends"),
("a76", "location"),
("a76", "manifest_anexos"),
("a76", "manifest_drivers"),
("a76", "manifests"),
("a76", "multi_currency_types"),
("a76", "octave_balance"),
("a76", "packages"),
("a76", "packing_lists"),
("a76", "parts"),
("a76", "pedimento_config_additional"),
("a76", "pedimento_config_calculations"),
("a76", "pedimento_config_parameters"),
("a76", "pedimento_config_surcharges"),
("a76", "pedimento_config_update_rectification"),
("a76", "pedimento_config_updates"),
("a76", "pedimento_containers"),
("a76", "pedimento_contributions"),
("a76", "pedimento_customs_offices"),
("a76", "pedimento_dates"),
("a76", "pedimento_decrementables"),
("a76", "pedimento_guides"),
("a76", "pedimento_incrementables"),
("a76", "pedimento_indexes"),
("a76", "pedimento_packages"),
("a76", "pedimento_payments"),
("a76", "pedimento_rectification_destination"),
("a76", "pedimento_rectification_origin"),
("a76", "pedimento_seals"),
("a76", "pedimento_transport_carriers"),
("a76", "pedimento_transport_means"),
("a76", "pedimento_validation"),
("a76", "pedimentos"),
("a76", "permission_rule_oct"),
("a76", "permission_rule_octave"),
("a76", "ports"),
("a76", "prevalidators"),
("a76", "previous_fractions"),
("a76", "seal"),
("a76", "sectors"),
("a76", "signatures"),
("a76", "subassembly_entries"),
("a76", "trailer"),
("a76", "transporter"),
("a76", "unit_conversions"),
("a76", "units_of_measure"),
("a76", "units_of_measure_general"),
("a76", "us_tariff_fractions"),
("a76", "value_manifestations"),
("a76", "vehicle"),
("core", "company_roles"),
("core", "role_permissions"),
("core", "user_company_permissions"),
("core", "user_company_roles"),
("public", "warning_fractions"),
]
TABLES_COMPANY_ONLY: list[tuple[str, str]] = [
("a24", "inv_aphis_catalog"),
("a76", "company_address"),
("a76", "company_certification"),
("a76", "company_cfdi"),
("a76", "company_digital_certificate"),
("a76", "company_electronic_agent"),
("a76", "company_prevalidator"),
]
POLICY_TENANT_ONLY = "tenant_isolation"
POLICY_TENANT_COMPANY = "tenant_company_isolation"
POLICY_COMPANY_ONLY = "company_isolation"
def upgrade() -> None:
"""Habilita RLS con políticas de aislamiento por tenant_id / company_id."""
op.execute("CREATE SCHEMA IF NOT EXISTS app")
op.execute(
"""
CREATE OR REPLACE FUNCTION app.current_tenant_id() RETURNS INTEGER
LANGUAGE sql STABLE AS $$
SELECT NULLIF(current_setting('app.tenant_id', true), '')::INTEGER
$$
"""
)
op.execute(
"""
CREATE OR REPLACE FUNCTION app.current_company_id() RETURNS INTEGER
LANGUAGE sql STABLE AS $$
SELECT NULLIF(current_setting('app.company_id', true), '')::INTEGER
$$
"""
)
for schema, table in TABLES_TENANT_ONLY:
op.execute(f'ALTER TABLE "{schema}"."{table}" ENABLE ROW LEVEL SECURITY')
op.execute(f'ALTER TABLE "{schema}"."{table}" FORCE ROW LEVEL SECURITY')
op.execute(
f"""
CREATE POLICY {POLICY_TENANT_ONLY} ON "{schema}"."{table}"
USING (tenant_id = app.current_tenant_id())
WITH CHECK (tenant_id = app.current_tenant_id())
"""
)
for schema, table in TABLES_TENANT_AND_COMPANY:
op.execute(f'ALTER TABLE "{schema}"."{table}" ENABLE ROW LEVEL SECURITY')
op.execute(f'ALTER TABLE "{schema}"."{table}" FORCE ROW LEVEL SECURITY')
op.execute(
f"""
CREATE POLICY {POLICY_TENANT_COMPANY} ON "{schema}"."{table}"
USING (
tenant_id = app.current_tenant_id()
AND (
app.current_company_id() IS NULL
OR company_id = app.current_company_id()
)
)
WITH CHECK (
tenant_id = app.current_tenant_id()
AND (
app.current_company_id() IS NULL
OR company_id = app.current_company_id()
)
)
"""
)
for schema, table in TABLES_COMPANY_ONLY:
op.execute(f'ALTER TABLE "{schema}"."{table}" ENABLE ROW LEVEL SECURITY')
op.execute(f'ALTER TABLE "{schema}"."{table}" FORCE ROW LEVEL SECURITY')
op.execute(
f"""
CREATE POLICY {POLICY_COMPANY_ONLY} ON "{schema}"."{table}"
USING (
EXISTS (
SELECT 1 FROM a76.company c
WHERE c.id = "{schema}"."{table}".company_id
AND c.tenant_id = app.current_tenant_id()
)
AND (
app.current_company_id() IS NULL
OR company_id = app.current_company_id()
)
)
WITH CHECK (
EXISTS (
SELECT 1 FROM a76.company c
WHERE c.id = "{schema}"."{table}".company_id
AND c.tenant_id = app.current_tenant_id()
)
AND (
app.current_company_id() IS NULL
OR company_id = app.current_company_id()
)
)
"""
)
def downgrade() -> None:
"""Revierte: drop policies, deshabilita RLS y elimina helpers."""
for schema, table in TABLES_COMPANY_ONLY:
op.execute(
f'DROP POLICY IF EXISTS {POLICY_COMPANY_ONLY} ON "{schema}"."{table}"'
)
op.execute(f'ALTER TABLE "{schema}"."{table}" NO FORCE ROW LEVEL SECURITY')
op.execute(f'ALTER TABLE "{schema}"."{table}" DISABLE ROW LEVEL SECURITY')
for schema, table in TABLES_TENANT_AND_COMPANY:
op.execute(
f'DROP POLICY IF EXISTS {POLICY_TENANT_COMPANY} ON "{schema}"."{table}"'
)
op.execute(f'ALTER TABLE "{schema}"."{table}" NO FORCE ROW LEVEL SECURITY')
op.execute(f'ALTER TABLE "{schema}"."{table}" DISABLE ROW LEVEL SECURITY')
for schema, table in TABLES_TENANT_ONLY:
op.execute(
f'DROP POLICY IF EXISTS {POLICY_TENANT_ONLY} ON "{schema}"."{table}"'
)
op.execute(f'ALTER TABLE "{schema}"."{table}" NO FORCE ROW LEVEL SECURITY')
op.execute(f'ALTER TABLE "{schema}"."{table}" DISABLE ROW LEVEL SECURITY')
op.execute("DROP FUNCTION IF EXISTS app.current_company_id()")
op.execute("DROP FUNCTION IF EXISTS app.current_tenant_id()")
op.execute("DROP SCHEMA IF EXISTS app")

View File

@@ -1,39 +0,0 @@
"""extend company.logo for S3 keys
Revision ID: d4e5f6a7b8c9
Revises: ca7d3c4e8b2a
Create Date: 2026-04-02
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
revision: str = "d4e5f6a7b8c9"
down_revision: Union[str, None] = "ca7d3c4e8b2a"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
op.alter_column(
"company",
"logo",
existing_type=sa.String(length=255),
type_=sa.String(length=512),
existing_nullable=True,
schema="a76",
)
def downgrade() -> None:
op.alter_column(
"company",
"logo",
existing_type=sa.String(length=512),
type_=sa.String(length=255),
existing_nullable=True,
schema="a76",
)

View File

@@ -1,40 +0,0 @@
"""add_app_settings_table
Revision ID: e76_app_settings
Revises: c1a2b3d4e5f6
Create Date: 2026-03-27 16:10:00.000000
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = 'e76_app_settings'
down_revision = 'd4e5f6a7b8c9'
branch_labels = None
depends_on = None
def upgrade():
# Create a76.app_settings table
op.create_table(
'app_settings',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('tenant_id', sa.Integer(), nullable=True),
sa.Column('company_id', sa.Integer(), nullable=True),
sa.Column('settings', postgresql.JSONB(astext_type=sa.Text()), nullable=False, server_default='{}'),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
sa.ForeignKeyConstraint(['company_id'], ['a76.company.id'], ),
sa.ForeignKeyConstraint(['tenant_id'], ['core.tenants.id'], ),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('tenant_id', 'company_id', name='uq_app_settings_tenant_company'),
schema='a76'
)
op.create_index(op.f('ix_a76_app_settings_company_id'), 'app_settings', ['company_id'], unique=False, schema='a76')
op.create_index(op.f('ix_a76_app_settings_tenant_id'), 'app_settings', ['tenant_id'], unique=False, schema='a76')
def downgrade():
op.drop_index(op.f('ix_a76_app_settings_tenant_id'), table_name='app_settings', schema='a76')
op.drop_index(op.f('ix_a76_app_settings_company_id'), table_name='app_settings', schema='a76')
op.drop_table('app_settings', schema='a76')

View File

@@ -1,193 +0,0 @@
"""create expediente_archivo table and seed document types digitization catalog
Revision ID: f1a2b3c4d5e6
Revises: f7a8b9c0d1e2
Create Date: 2026-04-20 00:00:00.000000
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = "f1a2b3c4d5e6"
down_revision = "f7a8b9c0d1e2"
branch_labels = None
depends_on = None
# ---------------------------------------------------------------------------
# Seed data
# ---------------------------------------------------------------------------
DOCUMENT_TYPES = [
("168", "Calca o fotografía digital del NIV del vehículo."),
("169", "Aviso."),
("170", "Factura."),
("171", "Documento con el que se acredite la propiedad de la mercancía."),
("172", "Contratos."),
("176", "Documentación relacionada con la garantía otorgada en términos de los artículos 84."),
("177", "Identificación Oficial."),
("179", "Comprobante de domicilio."),
("184", "Documento que ampara el avaluó de las mercancías."),
("185", "Documentos de adjudicación judicial de las mercancías."),
("187", "Solicitud de retiro de mercancías que causaron abandono."),
("189", "Actas."),
("192", "Escritos."),
("420", "Certificado de peso o volumen."),
("421", "Comprobante de la importación temporal de la embarcación debidamente formalizado."),
("422", "Comprobante expedido por donataria."),
("423", "Consulta en la que conste que el vehículo no se encuentra reportado como robado,"),
("424", "Clave Unica del Registro de Población."),
("425", "Declaración de internación o extracción de cantidades en efectivo y/o documentos p"),
("426", "Declaración de operaciones que no confieren origen en países no parte de acuerdo"),
("427", "Declaración en la que se señalen los motivos por los que efectúa la devolución de m"),
("428", "Documentación con información que permita la identificación, análisis y control en tér"),
("429", "Documentación que acredite que acepta y subsana la irregularidad."),
("430", "Documentación que ampare la importación temporal del vehículo de que se trate."),
("431", "Documentación que compruebe que la adquisición de las mercancías fue efectuada "),
("433", "Documento con base en el cual se determine la procedencia y el origen de las merca"),
("434", "Documento con que se acredite el reintegro del IVA, en caso de que el contribuyente "),
("435", "Documentos previstos en la regla 8.7., fracciones I a IV de la Resolución del TLCAN."),
("436", "El Documento que compruebe el cumplimiento de las regulaciones y restricciones no "),
("438", "Guía aérea, conocimiento de embarque o carta de porte."),
("439", "Hoja con los datos de la matrícula y nombre del barco, el lugar donde se localiza y se "),
("440", "Manifiesto de carga."),
("441", "Oficios emitidos por autoridad."),
("442", "Pedimentos."),
("443", "Programa IMMEX."),
("444", "Relación de candados."),
("445", "Relación de certificados de origen."),
]
# ---------------------------------------------------------------------------
# Upgrade / Downgrade
# ---------------------------------------------------------------------------
def upgrade() -> None:
# -- Table -----------------------------------------------------------------
op.create_table(
"expediente_archivo",
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
sa.Column("e_document", sa.String(length=50), nullable=True),
sa.Column("num_operacion", sa.String(length=50), nullable=True),
sa.Column("tipo_documento", sa.String(length=10), nullable=True),
sa.Column("archivo_digitalizado_en", sa.String(length=500), nullable=True),
sa.Column("fecha_digitalizacion", sa.Date(), nullable=True),
sa.Column("agente_aduanal", sa.String(length=50), nullable=True),
sa.Column("pedimento", sa.String(length=21), nullable=True),
sa.Column("rfc_consulta", sa.String(length=13), nullable=True),
sa.Column("nombre_archivo", sa.String(length=255), nullable=True),
sa.Column("status", sa.String(length=20), nullable=True),
sa.Column("task_id", sa.String(length=255), nullable=True),
sa.Column("external_task_id", sa.String(length=255), nullable=True),
sa.Column("acuse_pdf_path", sa.String(length=500), nullable=True),
sa.Column("envio_xml_path", sa.String(length=500), nullable=True),
sa.Column("respuesta_xml_path", sa.String(length=500), nullable=True),
sa.Column("consulta_envio_xml_path", sa.String(length=500), nullable=True),
sa.Column("consulta_respuesta_xml_path", sa.String(length=500), nullable=True),
sa.Column("tenant_id", sa.Integer(), nullable=False),
sa.Column("company_id", sa.Integer(), nullable=False),
sa.Column("created_at", sa.DateTime(), server_default=sa.text("now()"), nullable=False),
sa.Column("updated_at", sa.DateTime(), server_default=sa.text("now()"), nullable=False),
sa.Column("deleted_at", sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(["company_id"], ["a76.company.id"]),
sa.ForeignKeyConstraint(["tenant_id"], ["core.tenants.id"]),
sa.PrimaryKeyConstraint("id", name="expediente_archivo_pkey"),
schema="a76",
)
op.create_index(
op.f("ix_a76_expediente_archivo_company_id"),
"expediente_archivo",
["company_id"],
unique=False,
schema="a76",
)
op.create_index(
op.f("ix_a76_expediente_archivo_tenant_id"),
"expediente_archivo",
["tenant_id"],
unique=False,
schema="a76",
)
op.create_index(
op.f("ix_a76_expediente_archivo_task_id"),
"expediente_archivo",
["task_id"],
unique=False,
schema="a76",
)
op.create_index(
op.f("ix_a76_expediente_archivo_external_task_id"),
"expediente_archivo",
["external_task_id"],
unique=False,
schema="a76",
)
# -- Seeds -----------------------------------------------------------------
bind = op.get_bind()
companies = (
bind.execute(sa.text("SELECT id, tenant_id FROM a76.company ORDER BY id"))
.mappings()
.all()
)
for company in companies:
for code, description in DOCUMENT_TYPES:
bind.execute(
sa.text(
"""
INSERT INTO a76.document_types_digitization
(tenant_id, company_id, code, description, active)
VALUES
(:tenant_id, :company_id, :code, :description, TRUE)
ON CONFLICT ON CONSTRAINT document_types_digitization_code_key
DO NOTHING
"""
),
{
"tenant_id": company["tenant_id"],
"company_id": company["id"],
"code": code,
"description": description,
},
)
def downgrade() -> None:
# -- Remove seeds ----------------------------------------------------------
bind = op.get_bind()
codes = [code for code, _ in DOCUMENT_TYPES]
placeholders = ", ".join(f":c{i}" for i in range(len(codes)))
params = {f"c{i}": code for i, code in enumerate(codes)}
bind.execute(
sa.text(
f"DELETE FROM a76.document_types_digitization WHERE code IN ({placeholders})"
),
params,
)
# -- Drop table ------------------------------------------------------------
op.drop_index(
op.f("ix_a76_expediente_archivo_external_task_id"),
table_name="expediente_archivo",
schema="a76",
)
op.drop_index(
op.f("ix_a76_expediente_archivo_task_id"),
table_name="expediente_archivo",
schema="a76",
)
op.drop_index(
op.f("ix_a76_expediente_archivo_tenant_id"),
table_name="expediente_archivo",
schema="a76",
)
op.drop_index(
op.f("ix_a76_expediente_archivo_company_id"),
table_name="expediente_archivo",
schema="a76",
)
op.drop_table("expediente_archivo", schema="a76")

View File

@@ -1,39 +0,0 @@
"""fix driver transporter_key length and validations
Revision ID: f7a8b9c0d1e2
Revises: e76_app_settings
Create Date: 2026-04-17 00:00:00.000000
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = "f7a8b9c0d1e2"
down_revision = "e76_app_settings"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.alter_column(
"driver",
"transporter_key",
schema="a76",
existing_type=sa.String(length=5),
type_=sa.String(length=30),
existing_nullable=False,
)
def downgrade() -> None:
op.alter_column(
"driver",
"transporter_key",
schema="a76",
existing_type=sa.String(length=30),
type_=sa.String(length=5),
existing_nullable=False,
)

View File

@@ -1,53 +0,0 @@
"""increase invoice_header who_processed and capture_user to 100 chars
Revision ID: g1b2c3d4e5f6
Revises: f7a8b9c0d1e2
Create Date: 2026-04-30 00:00:00.000000
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'g1b2c3d4e5f6'
down_revision = 'f7a8b9c0d1e2'
branch_labels = None
depends_on = None
def upgrade() -> None:
op.alter_column(
'invoice_header',
'who_processed',
existing_type=sa.String(length=20),
type_=sa.String(length=100),
existing_nullable=True,
schema='a76',
)
op.alter_column(
'invoice_header',
'capture_user',
existing_type=sa.String(length=20),
type_=sa.String(length=100),
existing_nullable=True,
schema='a76',
)
def downgrade() -> None:
op.alter_column(
'invoice_header',
'capture_user',
existing_type=sa.String(length=100),
type_=sa.String(length=20),
existing_nullable=True,
schema='a76',
)
op.alter_column(
'invoice_header',
'who_processed',
existing_type=sa.String(length=100),
type_=sa.String(length=20),
existing_nullable=True,
schema='a76',
)

View File

@@ -1,26 +0,0 @@
"""merge alembic heads for deployment
Revision ID: h1a2b3c4d5e6
Revises: c3d4e5f6a7b8, g1b2c3d4e5f6
Create Date: 2026-04-30 12:00:00.000000
"""
from typing import Sequence, Union
# revision identifiers, used by Alembic.
revision: str = "h1a2b3c4d5e6"
down_revision: Union[str, Sequence[str], None] = (
"c3d4e5f6a7b8",
"g1b2c3d4e5f6",
)
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
pass
def downgrade() -> None:
pass

View File

@@ -1,104 +0,0 @@
"""Fix VARCHAR(20) truncation errors in audit_log and discharges
Revision ID: i7j8k9l0m1n2
Revises: h1a2b3c4d5e6
Create Date: 2026-04-30 13:00:00.000000
Issues fixed:
- audit_log.system: String(20) → String(50)
- audit_log.operation_type: String(20) → String(50)
- discharges.cancelled_by: String(20) → String(100)
- octave_balance.octave_permit: String(20) → String(100)
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'i7j8k9l0m1n2'
down_revision = 'h1a2b3c4d5e6'
branch_labels = None
depends_on = None
def upgrade() -> None:
# Fix audit_log.system
op.alter_column(
'audit_logs',
'system',
existing_type=sa.String(length=20),
type_=sa.String(length=50),
existing_nullable=False,
schema='a76',
)
# Fix audit_log.operation_type
op.alter_column(
'audit_logs',
'operation_type',
existing_type=sa.String(length=20),
type_=sa.String(length=50),
existing_nullable=True,
schema='a76',
)
# Fix discharges.cancelled_by
op.alter_column(
'discharge_header',
'cancelled_by',
existing_type=sa.String(length=20),
type_=sa.String(length=100),
existing_nullable=True,
schema='a24',
)
# Fix octave_balance.octave_permit
op.alter_column(
'octave_balance',
'octave_permit',
existing_type=sa.String(length=20),
type_=sa.String(length=100),
existing_nullable=False,
schema='a76',
)
def downgrade() -> None:
# Revert octave_balance.octave_permit
op.alter_column(
'octave_balance',
'octave_permit',
existing_type=sa.String(length=100),
type_=sa.String(length=20),
existing_nullable=False,
schema='a76',
)
# Revert discharges.cancelled_by
op.alter_column(
'discharge_header',
'cancelled_by',
existing_type=sa.String(length=100),
type_=sa.String(length=20),
existing_nullable=True,
schema='a24',
)
# Revert audit_log.operation_type
op.alter_column(
'audit_logs',
'operation_type',
existing_type=sa.String(length=50),
type_=sa.String(length=20),
existing_nullable=True,
schema='a76',
)
# Revert audit_log.system
op.alter_column(
'audit_logs',
'system',
existing_type=sa.String(length=50),
type_=sa.String(length=20),
existing_nullable=False,
schema='a76',
)