Refactor database models to use server defaults for boolean and timestamp fields

- Updated `TimestampMixin` to use `server_default` for `created_at` and `updated_at` fields.
- Modified various models in the `fa_classes`, `doc_types_dig`, `ports`, `units_of_measure`, `invoices`, `parts`, `trailers`, `licenses`, `tenants`, and `user_tenant` modules to set `server_default` for boolean fields and other relevant fields.
- Adjusted the `trailer_types` model to change the schema from `a76` to `public`.
- Implemented database migration execution during application startup in `main.py`.
- Removed old Alembic migration execution logic from the entrypoint script.
- Updated seed data for units of measure and removed unused seed files.
This commit is contained in:
2026-01-14 15:54:37 -06:00
parent afb3669c59
commit 4079a0cc0f
21 changed files with 329 additions and 639 deletions

View File

@@ -1,333 +0,0 @@
"""create material_types table
Revision ID: 531bf8cdae06
Revises:
Create Date: 2025-10-19 18:23:39.613953
"""
# pylint: disable=no-member
from typing import Sequence, Union
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision: str = "531bf8cdae06"
down_revision: Union[str, Sequence[str], None] = None
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.create_table(
"containers",
sa.Column("key", sa.String(length=3), nullable=False),
sa.Column("description", sa.String(length=500), nullable=False),
sa.PrimaryKeyConstraint("key", name="containers_pkey"),
schema="public",
)
op.create_table(
"countries",
sa.Column("m3_key", sa.String(length=3), nullable=False),
sa.Column("mex_key", sa.String(length=2), nullable=False),
sa.Column("ame_key", sa.String(length=2), nullable=False),
sa.Column("description_es", sa.String(length=50), nullable=False),
sa.Column("description_en", sa.String(length=50), nullable=False),
sa.PrimaryKeyConstraint("m3_key", name="countries_pkey"),
schema="public",
)
op.create_index(
"ak_country_ame", "countries", ["ame_key"], unique=True, schema="public"
)
op.create_table(
"currency_types",
sa.Column("code", sa.String(length=3), nullable=False),
sa.Column("currency_name", sa.String(length=15), nullable=False),
sa.Column("country_description", sa.String(length=50), nullable=False),
sa.PrimaryKeyConstraint("code", name="currency_types_pkey"),
schema="public",
)
op.create_table(
"customs_sections",
sa.Column("customs_code", sa.String(length=3), nullable=False),
sa.Column("section_name", sa.String(length=255), nullable=False),
sa.PrimaryKeyConstraint("customs_code", name="customs_code_pkey"),
schema="public",
)
op.create_table(
"customs_warehouses",
sa.Column("key", sa.String(length=3), nullable=False),
sa.Column("customs", sa.String(length=100), nullable=False),
sa.Column("fiscalized_warehouse", sa.String(length=1000), nullable=False),
sa.PrimaryKeyConstraint("key", "customs", name="pk_customs_warehouse"),
schema="public",
)
op.create_table(
"incoterms",
sa.Column("code", sa.String(length=5), nullable=False),
sa.Column("description_es", sa.String(length=256), nullable=False),
sa.Column("description_en", sa.String(length=256), nullable=False),
sa.PrimaryKeyConstraint("code", name="incoterms_pkey"),
schema="public",
)
op.create_table(
"invoice_types",
sa.Column("key", sa.String(length=5), nullable=False),
sa.Column("description", sa.String(length=50), nullable=False),
sa.Column("note", sa.String(length=500), nullable=False),
sa.Column("type", sa.String(length=15), nullable=False),
sa.Column("operation", sa.String(length=5), nullable=False),
sa.PrimaryKeyConstraint("key", name="invoice_types_pkey"),
schema="public",
)
op.create_table(
"material_types",
sa.Column("key", sa.String(length=10), nullable=False),
sa.Column("type", sa.String(length=15), nullable=False),
sa.Column("description", sa.String(length=256), nullable=False),
sa.PrimaryKeyConstraint("key", name="material_types_pkey"),
schema="public",
)
op.create_table(
"payment_methods",
sa.Column("key", sa.String(length=2), nullable=False),
sa.Column("description", sa.String(length=100), nullable=False),
sa.PrimaryKeyConstraint("key", name="payment_methods_pkey"),
schema="public",
)
op.create_table(
"pedimento_codes",
sa.Column("code", sa.String(length=3), nullable=False),
sa.Column("description", sa.String(length=250), nullable=False),
sa.PrimaryKeyConstraint("code", name="pedimento_codes_pkey"),
schema="public",
)
op.create_table(
"pedimento_regimens",
sa.Column("code", sa.String(length=3), nullable=False),
sa.Column("description", sa.String(length=100), nullable=False),
sa.PrimaryKeyConstraint("code", name="pedimento_regimens_pkey"),
schema="public",
)
op.create_table(
"sectors",
sa.Column("key", sa.String(length=8), nullable=False),
sa.Column("description", sa.String(length=150), nullable=False),
sa.Column("authorized", sa.SmallInteger(), nullable=False),
sa.PrimaryKeyConstraint("key", name="sectors_pkey"),
schema="public",
)
op.create_table(
"states",
sa.Column("m3_key", sa.String(length=3), nullable=False),
sa.Column("description", sa.String(length=50), nullable=False),
sa.Column("mex_key", sa.String(length=3), nullable=True),
sa.Column("ame_key", sa.String(length=2), nullable=True),
sa.PrimaryKeyConstraint("m3_key", "description", name="states_pkey"),
schema="public",
)
op.create_table(
"transport_modes",
sa.Column("key", sa.String(length=3), nullable=False),
sa.Column("name", sa.String(length=30), nullable=False),
sa.PrimaryKeyConstraint("key", name="transport_modes_pkey"),
schema="public",
)
op.create_table(
"transport_types",
sa.Column("transport_code", sa.String(length=2), nullable=False),
sa.Column("description", sa.String(length=100), nullable=False),
sa.PrimaryKeyConstraint("transport_code", name="transport_types_pkey"),
schema="public",
)
op.create_table(
"valuation_methods",
sa.Column("key", sa.String(length=2), nullable=False),
sa.Column("description", sa.String(length=200), nullable=False),
sa.PrimaryKeyConstraint("key", name="valuation_methods_pkey"),
schema="public",
)
op.create_table(
"code_pedimento_regimens",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("pedimento_code", sa.String(length=3), nullable=False),
sa.Column("regimen_code", sa.String(length=3), nullable=False),
sa.Column("type_code", sa.String(length=1), nullable=True),
sa.ForeignKeyConstraint(
["pedimento_code"], ["public.pedimento_codes.code"], name="fk_codeped"
),
sa.ForeignKeyConstraint(
["regimen_code"], ["public.pedimento_regimens.code"], name="fk_regimenped"
),
sa.PrimaryKeyConstraint("id", name="clave_pedimento_regimens_pkey"),
schema="public",
)
# Tablas de Unidades de Medida (A76)
# 1. Tabla: unit_of_measure_ace (ACE Units)
op.create_table(
'unit_of_measure_ace',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('code', sa.String(length=4), nullable=False),
sa.Column('description', sa.String(length=49), nullable=True),
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.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('code', name='uq_uom_ace_code'),
schema='a76'
)
# 2. Tabla: unit_of_measure_oma (OMA Units)
op.create_table(
'unit_of_measure_oma',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('code', sa.String(length=10), nullable=False),
sa.Column('description', sa.String(length=200), nullable=True),
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.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('code', name='uq_uom_oma_code'),
schema='a76'
)
# 3. Tabla: unit_of_measure_american (American Units)
op.create_table(
'unit_of_measure_american',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('code', sa.String(length=3), nullable=False),
sa.Column('description', sa.String(length=40), nullable=True),
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.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('code', name='uq_uom_american_code'),
schema='a76'
)
# 4. Tabla: unit_of_measure_customs (Customs Units)
op.create_table(
'unit_of_measure_customs',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('code', sa.String(length=10), nullable=False),
sa.Column('description', sa.String(length=50), nullable=True),
sa.Column('scaii_unit_code', sa.String(length=5), nullable=True),
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.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('code', name='uq_uom_customs_code'),
schema='a76'
)
# 5. Tabla: units_of_measure (Main Unit of Measure)
op.create_table(
'units_of_measure',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('code', sa.String(length=10), nullable=False),
sa.Column('description', sa.String(length=100), nullable=True),
sa.Column('description_en', sa.String(length=100), nullable=True),
sa.Column('customs_code', sa.String(length=10), nullable=True),
sa.Column('american_code', sa.String(length=3), nullable=True),
sa.Column('ace_code', sa.String(length=4), nullable=True),
sa.Column('oma_code', sa.String(length=10), 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(
['customs_code'],
['a76.unit_of_measure_customs.code'],
name='fk_uom_customs'
),
sa.ForeignKeyConstraint(
['american_code'],
['a76.unit_of_measure_american.code'],
name='fk_uom_american'
),
sa.ForeignKeyConstraint(
['ace_code'],
['a76.unit_of_measure_ace.code'],
name='fk_uom_ace'
),
sa.ForeignKeyConstraint(
['oma_code'],
['a76.unit_of_measure_oma.code'],
name='fk_uom_oma'
),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('code', 'tenant_id', 'company_id', name='uq_uom_code'),
schema='a76'
)
# 6. Tabla: units_of_measure_general (General/Conversion Units)
op.create_table(
'units_of_measure_general',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('code', sa.String(length=10), nullable=False),
sa.Column('description', sa.String(length=100), nullable=True),
sa.Column('conversion_factor', sa.Numeric(precision=13, scale=6), nullable=True),
sa.Column('mexico_unit', sa.String(length=10), nullable=True),
sa.Column('american_unit_code', sa.String(length=5), nullable=True),
sa.Column('customs_code', sa.String(length=10), nullable=True),
sa.Column('ace_code', sa.String(length=4), 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.ForeignKeyConstraint(
['customs_code'],
['a76.unit_of_measure_customs.code'],
name='fk_uom_general_customs'
),
sa.ForeignKeyConstraint(
['ace_code'],
['a76.unit_of_measure_ace.code'],
name='fk_uom_general_ace'
),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('code', 'tenant_id', 'company_id', name='uq_uom_general_code'),
schema='a76'
)
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
# Eliminar tablas de unidades de medida
op.drop_table('units_of_measure_general', schema='a76')
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')
# Eliminar tablas públicas
op.drop_table("code_pedimento_regimens", schema="public")
op.drop_table("valuation_methods", schema="public")
op.drop_table("transport_types", schema="public")
op.drop_table("transport_modes", schema="public")
op.drop_table("states", schema="public")
op.drop_table("sectors", schema="public")
op.drop_table("pedimento_regimens", schema="public")
op.drop_table("pedimento_codes", 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_index("ak_country_ame", table_name="countries", schema="public")
op.drop_table("countries", schema="public")
op.drop_table("containers", schema="public")
# ### end Alembic commands ###

View File

@@ -50,18 +50,37 @@ from api.v1.modules.public.reference_data.transport_modes.seed import (
from api.v1.modules.public.reference_data.transport_types.seed import (
seed as transport_types_seed,
)
from api.v1.modules.public.reference_data.trailer_types.seed import (
seed as trailer_types_seed,
)
from api.v1.modules.public.reference_data.valuation_methods.seed import (
seed as valuation_methods_seed,
)
from api.v1.modules.a76.general_catalogs.units_of_measure.seed_med import seed as units_of_measure_seed
from api.v1.modules.a76.general_catalogs.units_of_measure.seed_ace import seed as ace_seed
from api.v1.modules.a76.general_catalogs.units_of_measure.seed_oma import seed as oma_seed
from api.v1.modules.a76.general_catalogs.units_of_measure.seed_ame import seed as ame_seed
from api.v1.modules.a76.general_catalogs.units_of_measure.seed_adua import seed as adua_seed
from api.v1.modules.a76.general_catalogs.units_of_measure.seed import (
seed as units_of_measure_seed,
)
from api.v1.modules.a76.general_catalogs.units_of_measure.seed_ace import (
seed as ace_seed,
)
from api.v1.modules.a76.general_catalogs.units_of_measure.seed_oma import (
seed as oma_seed,
)
from api.v1.modules.a76.general_catalogs.units_of_measure.seed_ame import (
seed as ame_seed,
)
from api.v1.modules.a76.general_catalogs.units_of_measure.seed_adua import (
seed as adua_seed,
)
from api.v1.modules.core.permissions.seed import (
seed_invoices,
seed_user,
seed_report,
seed_roles,
)
# revision identifiers, used by Alembic.
revision: str = "7937209f9718"
down_revision: Union[str, Sequence[str], None] = "531bf8cdae06"
down_revision: Union[str, Sequence[str], None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
@@ -69,25 +88,10 @@ depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# --- AMPLIAR COLUMNAS ANTES DE INSERTAR DATOS ---
op.execute("ALTER TABLE a76.unit_of_measure_ace ALTER COLUMN code TYPE VARCHAR(20);")
op.execute("ALTER TABLE a76.unit_of_measure_oma ALTER COLUMN code TYPE VARCHAR(20);")
op.execute("ALTER TABLE a76.unit_of_measure_american ALTER COLUMN code TYPE VARCHAR(20);")
op.execute("ALTER TABLE a76.unit_of_measure_customs ALTER COLUMN code TYPE VARCHAR(20);")
op.execute("ALTER TABLE a76.units_of_measure ALTER COLUMN code TYPE VARCHAR(20);")
op.execute("ALTER TABLE a76.units_of_measure ALTER COLUMN customs_code TYPE VARCHAR(20);")
op.execute("ALTER TABLE a76.units_of_measure ALTER COLUMN american_code TYPE VARCHAR(20);")
op.execute("ALTER TABLE a76.units_of_measure ALTER COLUMN ace_code TYPE VARCHAR(20);")
op.execute("ALTER TABLE a76.units_of_measure ALTER COLUMN oma_code TYPE VARCHAR(20);")
# --- AGREGAR COLUMNAS deleted_at ---
op.execute("ALTER TABLE a76.units_of_measure ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMP;")
op.execute("ALTER TABLE a76.units_of_measure_general ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMP;")
# --- UTILIDAD DE FORMATEO ---
def format_value(val):
if val is None or str(val).strip() == '' or str(val).upper() == 'NONE':
return 'NULL'
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)}'"
# --- SEEDS PUBLIC (Tablas base) ---
@@ -302,6 +306,20 @@ def upgrade() -> None:
"""
)
values_trailert = ", ".join(
[
f"('{code}', '{desc.replace(chr(39), chr(39)*2)}')"
for code, desc in trailer_types_seed
]
)
op.execute(
f"""
INSERT INTO public.trailer_type (trailer_type_key, description) VALUES
{values_trailert}
ON CONFLICT (trailer_type_key) DO NOTHING;
"""
)
values_vm = ", ".join(
[
f"('{key}', '{desc.replace(chr(39), chr(39)*2)}')"
@@ -319,94 +337,159 @@ 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])
op.execute(f"INSERT INTO a76.unit_of_measure_ace (code, description) VALUES {val_ace} ON CONFLICT ON CONSTRAINT uq_uom_ace_code DO NOTHING;")
val_ace = ", ".join(
[f"({format_value(c)}, {format_value(d)})" for c, d in ace_seed]
)
op.execute(
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])
op.execute(f"INSERT INTO a76.unit_of_measure_oma (code, description) VALUES {val_oma} ON CONFLICT ON CONSTRAINT uq_uom_oma_code DO NOTHING;")
val_oma = ", ".join(
[f"({format_value(c)}, {format_value(d)})" for c, d in oma_seed]
)
op.execute(
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])
op.execute(f"INSERT INTO a76.unit_of_measure_american (code, description) VALUES {val_ame} ON CONFLICT ON CONSTRAINT uq_uom_american_code DO NOTHING;")
val_ame = ", ".join(
[f"({format_value(c)}, {format_value(d)})" for c, d in ame_seed]
)
op.execute(
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)
val_adua = ", ".join([f"({format_value(c)}, {format_value(d)})" for c, d in adua_seed])
op.execute(f"INSERT INTO a76.unit_of_measure_customs (code, description) VALUES {val_adua} ON CONFLICT ON CONSTRAINT uq_uom_customs_code DO NOTHING;")
val_adua = ", ".join(
[f"({format_value(c)}, {format_value(d)})" for c, d in adua_seed]
)
op.execute(
f"INSERT INTO a76.unit_of_measure_customs (code, description) 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()
additional_oma = set()
existing_customs = {c for c, d in adua_seed}
existing_american = {c for c, d in ame_seed}
existing_ace = {c for c, d in ace_seed}
existing_oma = {c for c, d in oma_seed}
for code, desc, desc_en, customs, american, ace, oma in units_of_measure_seed:
if customs and customs.strip() and customs not in existing_customs:
additional_customs.add((customs, f'Auto-generated from {code}'))
additional_customs.add((customs, f"Auto-generated from {code}"))
if american and american.strip() and american not in existing_american:
additional_american.add((american, f'Auto-generated from {code}'))
additional_american.add((american, f"Auto-generated from {code}"))
if ace and ace.strip() and ace not in existing_ace:
additional_ace.add((ace, f'Auto-generated from {code}'))
additional_ace.add((ace, f"Auto-generated from {code}"))
if oma and oma.strip() and oma not in existing_oma:
additional_oma.add((oma, f'Auto-generated from {code}'))
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)})" for c, d in additional_customs])
op.execute(f"INSERT INTO a76.unit_of_measure_customs (code, description) VALUES {val_add_customs} ON CONFLICT ON CONSTRAINT uq_uom_customs_code DO NOTHING;")
val_add_customs = ", ".join(
[f"({format_value(c)}, {format_value(d)})" 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;"
)
if additional_american:
val_add_american = ", ".join([f"({format_value(c)}, {format_value(d)})" for c, d in additional_american])
op.execute(f"INSERT INTO a76.unit_of_measure_american (code, description) VALUES {val_add_american} ON CONFLICT ON CONSTRAINT uq_uom_american_code DO NOTHING;")
val_add_american = ", ".join(
[f"({format_value(c)}, {format_value(d)})" for c, d in additional_american]
)
op.execute(
f"INSERT INTO a76.unit_of_measure_american (code, description) VALUES {val_add_american} ON CONFLICT ON CONSTRAINT uq_uom_american_code DO NOTHING;"
)
if additional_ace:
val_add_ace = ", ".join([f"({format_value(c)}, {format_value(d)})" for c, d in additional_ace])
op.execute(f"INSERT INTO a76.unit_of_measure_ace (code, description) VALUES {val_add_ace} ON CONFLICT ON CONSTRAINT uq_uom_ace_code DO NOTHING;")
val_add_ace = ", ".join(
[f"({format_value(c)}, {format_value(d)})" for c, d in additional_ace]
)
op.execute(
f"INSERT INTO a76.unit_of_measure_ace (code, description) VALUES {val_add_ace} ON CONFLICT ON CONSTRAINT uq_uom_ace_code DO NOTHING;"
)
if additional_oma:
val_add_oma = ", ".join([f"({format_value(c)}, {format_value(d)})" for c, d in additional_oma])
op.execute(f"INSERT INTO a76.unit_of_measure_oma (code, description) VALUES {val_add_oma} ON CONFLICT ON CONSTRAINT uq_uom_oma_code DO NOTHING;")
val_add_oma = ", ".join(
[f"({format_value(c)}, {format_value(d)})" for c, d in additional_oma]
)
op.execute(
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
val_uom = ", ".join([
f"({format_value(code)}, {format_value(desc)}, {format_value(desc_en)}, "
f"{format_value(customs)}, {format_value(american)}, {format_value(ace)}, {format_value(oma)}, 1, 1)"
for code, desc, desc_en, customs, american, ace, oma in units_of_measure_seed
])
op.execute(f"""
# TODO: Generar tenant_id y company_id correctos
val_uom = ", ".join(
[
f"({format_value(code)}, {format_value(desc)}, {format_value(desc_en)}, "
f"{format_value(customs)}, {format_value(american)}, {format_value(ace)}, {format_value(oma)}, 1, 1)"
for code, desc, desc_en, customs, american, ace, oma in units_of_measure_seed
]
)
op.execute("ALTER TABLE a76.units_of_measure DISABLE TRIGGER ALL;")
op.execute(
f"""
INSERT INTO a76.units_of_measure
(code, description, description_en, customs_code, american_code, ace_code, oma_code, tenant_id, company_id)
VALUES {val_uom}
ON CONFLICT (code, tenant_id, company_id) DO NOTHING;
""")
"""
)
op.execute("ALTER TABLE a76.units_of_measure ENABLE TRIGGER ALL;")
# --- SEEDS CORE (Permissions) ---
# Combinar todas las seeds de permisos
all_permissions = seed_invoices + seed_user + seed_report + seed_roles
values_permissions = ", ".join(
[
f"({format_value(code)}, {format_value(description)}, {format_value(module)}, {format_value(action)})"
for code, description, module, action in all_permissions
]
)
if values_permissions:
op.execute(
f"""
INSERT INTO core.permissions (code, description, module, action)
VALUES {values_permissions}
ON CONFLICT (code) DO NOTHING;
"""
)
def downgrade() -> None:
"""Downgrade schema."""
op.execute("DELETE FROM a76.units_of_measure;")
op.execute("DELETE FROM a76.unit_of_measure_customs;")
op.execute("DELETE FROM a76.unit_of_measure_american;")
op.execute("DELETE FROM a76.unit_of_measure_oma;")
op.execute("DELETE FROM a76.unit_of_measure_ace;")
op.execute("DELETE FROM public.valuation_methods;")
op.execute("DELETE FROM public.transport_types;")
op.execute("DELETE FROM public.transport_modes;")
op.execute("DELETE FROM public.sectors;")
op.execute("DELETE FROM public.payment_methods;")
op.execute("DELETE FROM public.material_types;")
op.execute("DELETE FROM public.invoice_types;")
op.execute("DELETE FROM public.incoterms;")
op.execute("DELETE FROM public.customs_warehouses;")
op.execute("DELETE FROM public.customs_sections;")
op.execute("DELETE FROM public.currency_types;")
op.execute("DELETE FROM public.countries;")
op.execute("DELETE FROM public.containers;")
op.execute("DELETE FROM public.code_pedimento_regimens;")
op.execute("DELETE FROM public.pedimento_regimens;")
op.execute("DELETE FROM public.pedimento_codes;")
op.drop_table("valuation_methods", schema="public")
op.drop_table("transport_types", schema="public")
op.drop_table("transport_modes", schema="public")
op.drop_table("sectors", 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")

View File

@@ -9,10 +9,10 @@ class TimestampMixin:
"""Mixin for common timestamp fields"""
created_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now()
DateTime, nullable=False, server_default=func.now()
)
updated_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now(), onupdate=func.now()
DateTime, nullable=False, server_default=func.now(), onupdate=func.now()
)
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)

View File

@@ -18,18 +18,32 @@ class QClasses(Base, TenantScopedMixin, TimestampMixin):
__tablename__ = "fa_classes" # QClases
__table_args__ = (
PrimaryKeyConstraint("id", name="qclases_pk"),
ForeignKeyConstraint(["class_id"], ["a76.classes.id"], name="fk_qclasses_classes"),
ForeignKeyConstraint(
["class_id"], ["a76.classes.id"], name="fk_qclasses_classes"
),
{"schema": "a24"},
)
id: Mapped[int] = mapped_column(Integer, primary_key=True)
class_id: Mapped[int] = mapped_column(Integer, nullable=False)
import_tariff_code: Mapped[Optional[str]] = mapped_column(String(10), nullable=True) # FRACCIONIMPO
import_tariff_type: Mapped[Optional[str]] = mapped_column(String(6), nullable=True) # TIPOFRACIMPO
export_tariff_code: Mapped[Optional[str]] = mapped_column(String(10), nullable=True) # FRACCIONEXPO
export_tariff_type: Mapped[Optional[str]] = mapped_column(String(6), nullable=True) # TIPOFRACEXPO
depreciation_rate: Mapped[Optional[Decimal]] = mapped_column(Numeric(5, 2), nullable=True) # TASADEPRECIA
import_tariff_code: Mapped[Optional[str]] = mapped_column(
String(10), nullable=True
) # FRACCIONIMPO
import_tariff_type: Mapped[Optional[str]] = mapped_column(
String(6), nullable=True
) # TIPOFRACIMPO
export_tariff_code: Mapped[Optional[str]] = mapped_column(
String(10), nullable=True
) # FRACCIONEXPO
export_tariff_type: Mapped[Optional[str]] = mapped_column(
String(6), nullable=True
) # TIPOFRACEXPO
depreciation_rate: Mapped[Optional[Decimal]] = mapped_column(
Numeric(5, 2), nullable=True
) # TASADEPRECIA
fda_code: Mapped[Optional[str]] = mapped_column(String(20), nullable=True) # FDA
eccn_code: Mapped[Optional[str]] = mapped_column(String(20), nullable=True) # ECCN
class_enabled: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) # HABILITADESHABILITACLASE
class_enabled: Mapped[bool] = mapped_column(
Boolean, default=True, server_default="true", nullable=False
) # HABILITADESHABILITACLASE

View File

@@ -1,6 +1,13 @@
from api.v1.common.base_models import TenantScopedMixin, TimestampMixin
from core.database import Base
from sqlalchemy import Boolean, Integer, PrimaryKeyConstraint, String, Text, UniqueConstraint
from sqlalchemy import (
Boolean,
Integer,
PrimaryKeyConstraint,
String,
Text,
UniqueConstraint,
)
from sqlalchemy.orm import Mapped, mapped_column
@@ -22,4 +29,4 @@ class DocumentTypeDigitization(Base, TenantScopedMixin, TimestampMixin):
id: Mapped[int] = mapped_column(Integer)
code: Mapped[str] = mapped_column(String(10), nullable=False, index=True)
description: Mapped[str] = mapped_column(Text, nullable=False)
active: Mapped[bool] = mapped_column(Boolean, default=True)
active: Mapped[bool] = mapped_column(Boolean, default=True, server_default="true")

View File

@@ -11,7 +11,7 @@ class PortBase(BaseModel):
location_description: Optional[str] = Field(
None, max_length=20, description="Location Description")
port_type: PortType = Field(
default=PortType.ENTRY, description="Port Type (ENTRY, EXIT, BOTH)")
server_default=PortType.ENTRY, description="Port Type (ENTRY, EXIT, BOTH)")
class PortCreate(PortBase):

View File

@@ -32,4 +32,4 @@ class Port(Base, TenantScopedMixin, TimestampMixin):
# New column requested
port_type: Mapped[PortType] = mapped_column(
String(15), nullable=False, default=PortType.ENTRY)
String(15), nullable=False, server_default=PortType.ENTRY)

View File

@@ -13,8 +13,6 @@ from api.v1.common.base_models import TenantScopedMixin, TimestampMixin
from core.database import Base
# 1. GUniMedACE
class UnitOfMeasureACE(Base, TimestampMixin):
__tablename__ = "unit_of_measure_ace"
__table_args__ = (
@@ -28,8 +26,6 @@ class UnitOfMeasureACE(Base, TimestampMixin):
# 2. GUMOMA
class UnitOfMeasureOMA(Base, TimestampMixin):
__tablename__ = "unit_of_measure_oma"
__table_args__ = (
@@ -43,8 +39,6 @@ class UnitOfMeasureOMA(Base, TimestampMixin):
# 3. GUMAme
class UnitOfMeasureAmerican(Base, TimestampMixin):
__tablename__ = "unit_of_measure_american"
__table_args__ = (
@@ -58,8 +52,6 @@ class UnitOfMeasureAmerican(Base, TimestampMixin):
# 4. GUMAduana
class UnitOfMeasureCustoms(Base, TimestampMixin):
__tablename__ = "unit_of_measure_customs"
__table_args__ = (
@@ -68,52 +60,15 @@ class UnitOfMeasureCustoms(Base, TimestampMixin):
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
code: Mapped[str] = mapped_column(String(2), nullable=False) # CLAVE
description: Mapped[Optional[str]] = mapped_column(String(20), nullable=True)
scaii_unit_code: Mapped[Optional[str]] = mapped_column(
String(5), nullable=True
) # UNIDADSCAII
code: Mapped[str] = mapped_column(Integer, nullable=False) # CLAVE
description: Mapped[Optional[str]] = mapped_column(String(20), nullable=True)
# 5. GUniMedida (Main)
class UnitOfMeasure(Base, TenantScopedMixin, TimestampMixin):
__tablename__ = "units_of_measure"
__table_args__ = (
UniqueConstraint("code", "tenant_id", "company_id", name="uq_uom_code"),
ForeignKeyConstraint(
["customs_code"],
[
"a76.unit_of_measure_customs.code"
],
use_alter=True,
name="fk_uom_customs",
),
ForeignKeyConstraint(
["american_code"],
[
"a76.unit_of_measure_american.code"
],
use_alter=True,
name="fk_uom_american",
),
ForeignKeyConstraint(
["ace_code"],
[
"a76.unit_of_measure_ace.code"
],
use_alter=True,
name="fk_uom_ace",
),
ForeignKeyConstraint(
["oma_code"],
[
"a76.unit_of_measure_oma.code"
],
use_alter=True,
name="fk_uom_oma",
),
UniqueConstraint("code", "tenant_id", "company_id", name="uq_uom_code"),
{"schema": "a76", "extend_existing": True},
)
@@ -122,35 +77,19 @@ class UnitOfMeasure(Base, TenantScopedMixin, TimestampMixin):
description: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
description_en: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
customs_code: Mapped[Optional[str]] = mapped_column(
String(2), nullable=True
) # CLAVE_AMEX
american_code: Mapped[Optional[str]] = mapped_column(
String(3), nullable=True
) # CLAVE_AAMER
ace_code: Mapped[Optional[str]] = mapped_column(
String(4), nullable=True
) # CLAVEACE
oma_code: Mapped[Optional[str]] = mapped_column(
String(10), nullable=True
) # CLAVEOMA
customs_code: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.unit_of_measure_customs.code"), nullable=True) # CLAVE_AMEX
american_code: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.unit_of_measure_american.code"), nullable=True) # CLAVE_AAMER
ace_code: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.unit_of_measure_ace.code"), nullable=True) # CLAVEACE
oma_code: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.unit_of_measure_oma.code"), nullable=True) # CLAVEOMA
# Relationships omitted for simplicity or need explicit primaryjoin
customs_unit: Mapped[Optional["UnitOfMeasureCustoms"]] = relationship()
american_unit: Mapped[Optional["UnitOfMeasureAmerican"]] = relationship(
overlaps="customs_unit"
)
ace_unit: Mapped[Optional["UnitOfMeasureACE"]] = relationship(
overlaps="american_unit,customs_unit"
)
oma_unit: Mapped[Optional["UnitOfMeasureOMA"]] = relationship(
overlaps="ace_unit,american_unit,customs_unit"
)
american_unit: Mapped[Optional["UnitOfMeasureAmerican"]] = relationship(overlaps="customs_unit")
ace_unit: Mapped[Optional["UnitOfMeasureACE"]] = relationship(overlaps="american_unit,customs_unit")
oma_unit: Mapped[Optional["UnitOfMeasureOMA"]] = relationship(overlaps="ace_unit,american_unit,customs_unit")
# 6. GUniMed (General/Conversion)
class UnitOfMeasureGeneral(Base, TenantScopedMixin, TimestampMixin):
__tablename__ = "units_of_measure_general"
__table_args__ = (
@@ -178,14 +117,10 @@ class UnitOfMeasureGeneral(Base, TenantScopedMixin, TimestampMixin):
)
mexico_unit: Mapped[Optional[str]] = mapped_column(String(5), nullable=True)
# UNIDAD_AME (Note: GUniMed has UNIDAD_AME varchar(5), but GUMAme has CLAVE varchar(3). Keeping as string for now)
american_unit_code: Mapped[Optional[str]] = mapped_column(String(5), nullable=True)
american_unit_code: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.unit_of_measure_american.code"), nullable=True)
customs_code: Mapped[Optional[str]] = mapped_column(
String(2), nullable=True
) # CLAVE_ADUANA
ace_code: Mapped[Optional[str]] = mapped_column(
String(4), nullable=True
) # CLAVEACE
customs_code: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.unit_of_measure_customs.code"), nullable=True) # CLAVE_ADUANA
ace_code: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.unit_of_measure_ace.code"), nullable=True) # CLAVEACE
customs_unit: Mapped[Optional["UnitOfMeasureCustoms"]] = relationship()
ace_unit: Mapped[Optional["UnitOfMeasureACE"]] = relationship(

View File

@@ -1,24 +1,24 @@
seed = [
('KGS', 'Kilo'),
('KW', 'Kilowatt'),
('MILLR', 'Millar'),
('JGO', 'Juego'),
('KWH', 'Kilowatt/Hora'),
('TON', 'Tonelada'),
('BARR', 'Barril'),
('GRN', 'Gramo Neto'),
('DEC', 'Decenas'),
('CIEN', 'Cientos'),
('DOCE', 'Decenas (Docenas)'),
('GR', 'Gramo'),
('CAJA', 'Caja'),
('PZA', 'Botella'),
('CARAT', 'Carat'),
('MT', 'Metro Lineal'),
('M2', 'Metro Cuadrado'),
('M3', 'Metro Cubico'),
('PZA', 'Pieza'),
('PZA', 'Cabeza'),
('LT', 'Litro'),
('PAR', 'Par'),
("1", "Kilo"),
("2", "Gramo"),
("3", "Metro Lineal"),
("4", "Metro Cuadrado"),
("5", "Metro Cubico"),
("6", "Pieza"),
("7", "Cabeza"),
("8", "Litro"),
("9", "Par"),
("10", "Kilowatt"),
("11", "Millar"),
("12", "Juego"),
("13", "Kilowatt/Hora"),
("14", "Tonelada"),
("15", "Barril"),
("16", "Gramo Neto"),
("17", "Decenas"),
("18", "Cientos"),
("19", "Decenas"),
("20", "Caja"),
("21", "Botella"),
("22", "Carat"),
]

View File

@@ -1,44 +0,0 @@
seed = [
# (code, desc_es, desc_en, customs, american, ace, oma)
('BARR', 'BARRIL', 'BARIEL', '', 'BBL', 'BLL', ''),
('BD FT', 'PIE TABLA', 'BD FEET', '', 'FT', 'BFT', ''),
('BOLS', 'BOLSA', 'BAG', '', 'PCS', 'BG', ''),
('BTL', 'BOTELLA', 'BOTTLE', '', 'PCS', 'BO', ''),
('BULT', 'BULTO', 'BULK', '', 'PCS', 'VQ', ''),
('CAJA', 'CAJA', 'BOX', '', '', 'BX', ''),
('CARAT', 'CARAT', 'CARAT', '', '', 'HE', ''),
('CBZA', 'CABEZA', 'HEAD', '', 'PCS', 'Z4', ''),
('CIEN', 'CIENTO', 'CIEN', '', '', 'CEN', ''),
('CM', 'CENTIMETRO', 'CM', 'CM', 'CM', 'CMT', ''),
('CM2', 'CENTIMETRO CUADRADO', 'CM2', 'CM2', 'CM2', 'CMK', ''),
('DEC', 'DECENA', '', '', '', 'DC', ''),
('DM', 'DECIMETRO', 'DM', '', '', 'DMT', ''),
('DM2', 'DECIMETRO CUADRADO', 'SQ DM', '', '', 'DMK', ''),
('DOCE', 'DOCENA', 'DOZ', '', 'DOZ', 'DZN', 'DZ'),
('FOZ', 'ONZA LIQUIDA', 'FOZ', 'FOZ', 'FOZ', 'OZA', ''),
('FT', 'PIES', 'FT', 'FT', 'FT', 'LF', ''),
('FT2', 'PIE CUADRADO', 'FT2', '', 'SFT', 'FTK', ''),
('GAL', 'GALON', 'GAL', 'GAL', 'GAL', 'GLL', ''),
('GR', 'GRAMO', 'GRAM', '', '', 'GRM', ''),
('IN', 'PULGADA', 'IN', '', '', 'LI', ''),
('IN2', 'PULGADA CUADRADA', 'IN2', '', '', 'INK', ''),
('JGO', 'JUEGO', 'SET', '', '', 'SET', ''),
('KGS', 'KILOGRAMOS', 'KGS', '', 'KG2', 'KGM', ''),
('LB', 'LIBRAS', 'LB', '', '', 'LBR', ''),
('LT', 'LITRO', 'LT', 'LT', 'L', 'LTR', ''),
('M2', 'METRO CUADRADO', 'M2', 'M2', 'M2', 'MTK', ''),
('M3', 'METRO CUBICO', 'M3', 'M3', 'M3', 'MTQ', ''),
('MI', 'MILLA', 'MILE', '', 'KM', 'SMI', ''),
('MILLR', 'MILLAR', 'MILLR', '', '', 'MIL', ''),
('MT', 'METROS', 'MT', 'MT', 'M', 'MTR', ''),
('OZ', 'ONZA', 'OZ', 'FOZ', 'FOZ', 'OZ', ''),
('PAR', 'PAR', 'PAIR', '', '', 'PB', ''),
('PQ', 'PAQUETE', 'PACKAGE', '', 'PCS', 'PK_1', ''),
('PZA', 'PIEZA', 'PCS', 'PCS', 'PCS', 'C62_1', ''),
('QGL', 'CUARTO DE GALON', 'QGL', '', '', 'QT', ''),
('ROLL', 'ROLLO', 'ROLL', '', '', 'RO', ''),
('TON', 'TONELADA', 'TON', 'TON', 'TON', 'TNE_1', ''),
('TOZ', 'ONZA TROY', 'TOZ', '', 'TOZ', 'APZ', ''),
('YD', 'YARDA', 'YD', 'YD', 'YD', 'YRD', ''),
('YD2', 'YARDA CUADRADA', 'YD2', '', 'SYD', 'YDK', ''),
]

View File

@@ -10,6 +10,7 @@ from sqlalchemy import (
String,
Text,
TIMESTAMP,
func,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
from core.database import Base
@@ -101,7 +102,7 @@ class InvoiceHeader(Base, TenantScopedMixin, TimestampMixin):
# Dates
invoice_date: Mapped[datetime] = mapped_column(Date) # FECHAFACTURA
capture_date: Mapped[datetime] = mapped_column(
TIMESTAMP(timezone=False), default=datetime.now
TIMESTAMP(timezone=False), server_default=func.now()
) # FECHACAPTURA + HORAACTUAL
emission_date: Mapped[Optional[datetime]] = mapped_column(Date) # FECHAEMISION
@@ -153,13 +154,13 @@ class InvoiceHeader(Base, TenantScopedMixin, TimestampMixin):
# Generation flags
generate_id: Mapped[Optional[bool]] = mapped_column(
Boolean, default=False
Boolean, default=False, server_default="false"
) # GENERAID
generate_desc_parties: Mapped[Optional[str]] = mapped_column(
String(12)
) # GENDESCPARTIDAS / Generar descripción de partidas
apply_manual_discount: Mapped[Optional[bool]] = mapped_column(
Boolean, default=False
Boolean, default=False, server_default="false"
) # APLICADESCMANUAL
# Bulk & Downloads
@@ -287,7 +288,7 @@ class InvoiceComplianceMx(Base, TenantScopedMixin, TimestampMixin):
Integer
) # APENDICE17 / Apéndice 17
is_regime_change: Mapped[Optional[bool]] = mapped_column(
Boolean, default=False
Boolean, default=False, server_default="false"
) # ESCAMBIOREGIMEN / Es cambio de régimen
which_exchange_rate: Mapped[Optional[str]] = mapped_column(
String(5)
@@ -299,15 +300,15 @@ class InvoiceComplianceMx(Base, TenantScopedMixin, TimestampMixin):
String(5)
) # ACTVALOR / Actualizar valor
is_pedimento_pending: Mapped[Optional[bool]] = mapped_column(
Boolean, default=False
Boolean, default=False, server_default="false"
) # PED_PENDIENTE_ASIGNAR (Mapear 1 -> True, 0 -> False)
# Ownership & Balances
is_owner_of_goods: Mapped[Optional[bool]] = mapped_column(
Boolean, default=False
Boolean, default=False, server_default="false"
) # ESDUENOMCIA / Es dueño de mercancía
generate_balances: Mapped[Optional[bool]] = mapped_column(
Boolean, default=False
Boolean, default=False, server_default="false"
) # GENERARSALDOS / Generar saldos
was_reviewed_by_company: Mapped[Optional[bool]] = mapped_column(
Boolean
@@ -413,102 +414,102 @@ class InvoiceFinancials(Base, TenantScopedMixin, TimestampMixin):
# Merchandise Values (MN = National Currency, ME = Foreign Currency, MC = Third Currency)
value_mn: Mapped[Optional[float]] = mapped_column(
Numeric(23, 8), default=0
Numeric(23, 8), default=0, server_default="0"
) # VALORIMPOMN/VALOREXPOMN/VALORENTMN/VALORSALMN
value_me: Mapped[Optional[float]] = mapped_column(
Numeric(23, 8), default=0
Numeric(23, 8), default=0, server_default="0"
) # VALORIMPOME/VALOREXPOME/VALORENTME/VALORSALME
value_mc: Mapped[Optional[float]] = mapped_column(
Numeric(23, 8), default=0
Numeric(23, 8), default=0, server_default="0"
) # VALORIMPOMC/VALOREXPOMC
# Customs Value
customs_value_mn: Mapped[Optional[float]] = mapped_column(
Numeric(23, 8), default=0
Numeric(23, 8), default=0, server_default="0"
) # VALORADUANASMN / Valor en aduanas MN
customs_value_me: Mapped[Optional[float]] = mapped_column(
Numeric(23, 8), default=0
Numeric(23, 8), default=0, server_default="0"
) # VALORADUANASME / Valor en aduanas ME
# Raw Materials
raw_material_value_mn: Mapped[Optional[float]] = mapped_column(
Numeric(23, 8), default=0
Numeric(23, 8), default=0, server_default="0"
) # VALORMPMN / Valor materia prima MN
raw_material_value_me: Mapped[Optional[float]] = mapped_column(
Numeric(23, 8), default=0
Numeric(23, 8), default=0, server_default="0"
) # VALORMPME / Valor materia prima ME
# Aggregate Value
aggregate_value_mn: Mapped[Optional[float]] = mapped_column(
Numeric(23, 8), default=0
Numeric(23, 8), default=0, server_default="0"
) # VALORAGREMN / Valor agregado MN
aggregate_value_me: Mapped[Optional[float]] = mapped_column(
Numeric(23, 8), default=0
Numeric(23, 8), default=0, server_default="0"
) # VALORAGREME / Valor agregado ME
aggregate_value_mc: Mapped[Optional[float]] = mapped_column(
Numeric(23, 8), default=0
Numeric(23, 8), default=0, server_default="0"
) # VALORAGREMC / Valor agregado MC
# Mexican Merchandise Value
mexican_value_mn: Mapped[Optional[float]] = mapped_column(
Numeric(23, 8), default=0
Numeric(23, 8), default=0, server_default="0"
) # VALORVMEXMN / Valor mercancía mexicana MN
mexican_value_me: Mapped[Optional[float]] = mapped_column(
Numeric(23, 8), default=0
Numeric(23, 8), default=0, server_default="0"
) # VALORVMEXME / Valor mercancía mexicana ME
mexican_value_mc: Mapped[Optional[float]] = mapped_column(
Numeric(23, 8), default=0
Numeric(23, 8), default=0, server_default="0"
) # VALORVMEXMC / Valor mercancía mexicana MC
# National Packaging
national_packaging_mn: Mapped[Optional[float]] = mapped_column(
Numeric(23, 8), default=0
Numeric(23, 8), default=0, server_default="0"
) # VALEMPAQUENACMN / Valor empaque nacional MN
national_packaging_me: Mapped[Optional[float]] = mapped_column(
Numeric(23, 8), default=0
Numeric(23, 8), default=0, server_default="0"
) # VALEMPAQUENACME / Valor empaque nacional ME
national_packaging_mc: Mapped[Optional[float]] = mapped_column(
Numeric(23, 8), default=0
Numeric(23, 8), default=0, server_default="0"
) # VALEMPAQUENACMC / Valor empaque nacional MC
# Costs & Increments
freight: Mapped[Optional[float]] = mapped_column(
Numeric(19, 8), default=0
Numeric(19, 8), default=0, server_default="0"
) # FLETE / Flete
insurance: Mapped[Optional[float]] = mapped_column(
Numeric(19, 8), default=0
Numeric(19, 8), default=0, server_default="0"
) # SEGUROS / Seguros
insurance_value: Mapped[Optional[float]] = mapped_column(
Numeric(19, 8), default=0
Numeric(19, 8), default=0, server_default="0"
) # VALSEGUROS / Valor seguros
packaging: Mapped[Optional[float]] = mapped_column(
Numeric(19, 8), default=0
Numeric(19, 8), default=0, server_default="0"
) # EMBALAJES / Embalajes
other_increments: Mapped[Optional[float]] = mapped_column(
Numeric(19, 8), default=0
Numeric(19, 8), default=0, server_default="0"
) # OTROSINCREMENTA / Otros incrementables
total_increments_mn: Mapped[Optional[float]] = mapped_column(
Numeric(23, 8), default=0
Numeric(23, 8), default=0, server_default="0"
) # TOTALINCREMMN / Total incrementables MN
total_increments_me: Mapped[Optional[float]] = mapped_column(
Numeric(23, 8), default=0
Numeric(23, 8), default=0, server_default="0"
) # TOTALINCREMME / Total incrementables ME
# Taxes
iva_mn: Mapped[Optional[float]] = mapped_column(
Numeric(23, 8), default=0
Numeric(23, 8), default=0, server_default="0"
) # IVAEXPOMN/VALORIVAMN / IVA en MN
iva_me: Mapped[Optional[float]] = mapped_column(
Numeric(23, 8), default=0
Numeric(23, 8), default=0, server_default="0"
) # IVAEXPOME/VALORIVAME / IVA en ME
iva_mc: Mapped[Optional[float]] = mapped_column(
Numeric(23, 8), default=0
Numeric(23, 8), default=0, server_default="0"
) # IVAEXPOMC / IVA en MC
iva_factor: Mapped[Optional[str]] = mapped_column(
String(10)
) # FACTORIVA / Factor IVA (puede ser varchar en imports)
tax_value_me: Mapped[Optional[float]] = mapped_column(
Numeric(23, 8), default=0
Numeric(23, 8), default=0, server_default="0"
) # VALORIMPUESTOME / Valor impuesto ME
seal_value_2500: Mapped[Optional[bool]] = mapped_column(
Boolean
@@ -554,7 +555,7 @@ class InvoiceLogistics(Base, TenantScopedMixin, TimestampMixin):
String(10)
) # TRANSPORTISTAAME / Transportista americano
transport_type: Mapped[TransportType] = mapped_column(
String(15), default="none"
String(15), server_default="none"
) # TRANSPORTE / Tipo de transporte
transport_num: Mapped[Optional[str]] = mapped_column(
String(20)
@@ -566,7 +567,7 @@ class InvoiceLogistics(Base, TenantScopedMixin, TimestampMixin):
String(80)
) # CONDUCTOR / Nombre del conductor
is_rail: Mapped[Optional[bool]] = mapped_column(
Boolean, default=False
Boolean, default=False, server_default="false"
) # ESFERROCARRIL / Es ferrocarril
rail_id: Mapped[Optional[str]] = mapped_column(
String(31)
@@ -655,7 +656,7 @@ class InvoiceLogistics(Base, TenantScopedMixin, TimestampMixin):
# Delivery Control
delivered_status: Mapped[Optional[bool]] = mapped_column(
Boolean, default=False
Boolean, default=False, server_default="false"
) # ENTREGADO / Estado de entrega
received_by: Mapped[Optional[str]] = mapped_column(
String(50)
@@ -671,7 +672,7 @@ class InvoiceLogistics(Base, TenantScopedMixin, TimestampMixin):
# CTM Process
is_ctm_process: Mapped[Optional[bool]] = mapped_column(
Boolean, default=False
Boolean, default=False, server_default="false"
) # SETRATAPROCESOCTM / Se trata de proceso CTM
# Relationship

View File

@@ -89,7 +89,9 @@ class Part(Base, TenantScopedMixin, TimestampMixin):
export_code: Mapped[Optional[str]] = mapped_column(String(2))
exclusion_symbol: Mapped[Optional[str]] = mapped_column(String(19))
is_active: Mapped[Optional[bool]] = mapped_column(Boolean, default=True)
is_active: Mapped[Optional[bool]] = mapped_column(
Boolean, default=True, server_default="true"
)
part_photo: Mapped[Optional[str]] = mapped_column(String(255))
creation_date: Mapped[Optional[int]] = mapped_column()

View File

@@ -12,7 +12,7 @@ class Trailer(Base, TenantScopedMixin, TimestampMixin):
trailer_number = Column(String(20), primary_key=True, nullable=False)
ace_trailer_number = Column(String(10), nullable=True)
trailer_type_key = Column(
String(2), ForeignKey("a76.trailer_type.trailer_type_key"), nullable=True
String(2), ForeignKey("public.trailer_type.trailer_type_key"), nullable=True
)
seal = Column(String(15), nullable=True)
entity_code = Column(String(1), nullable=True)

View File

@@ -44,21 +44,29 @@ class License(Base, TimestampMixin):
)
# Plan y características
plan = Column(SQLEnum(LicensePlan), default=LicensePlan.FREE, nullable=False)
plan = Column(
SQLEnum(LicensePlan),
default=LicensePlan.FREE,
server_default="FREE",
nullable=False,
)
status = Column(
SQLEnum(LicenseStatus), default=LicenseStatus.PENDING, nullable=False
SQLEnum(LicenseStatus),
default=LicenseStatus.PENDING,
server_default="PENDING",
nullable=False,
)
# Límites del plan
max_users = Column(Integer, default=5, nullable=False)
max_storage_gb = Column(Integer, default=10, nullable=False)
max_monthly_operations = Column(Integer, default=1000, nullable=False)
max_users = Column(Integer, server_default="5", nullable=False)
max_storage_gb = Column(Integer, server_default="10", nullable=False)
max_monthly_operations = Column(Integer, server_default="1000", nullable=False)
# Features habilitadas (booleans)
feature_api_access = Column(Boolean, default=True)
feature_advanced_reports = Column(Boolean, default=False)
feature_integrations = Column(Boolean, default=False)
feature_dedicated_support = Column(Boolean, default=False)
feature_api_access = Column(Boolean, default=True, server_default="true")
feature_advanced_reports = Column(Boolean, default=False, server_default="false")
feature_integrations = Column(Boolean, default=False, server_default="false")
feature_dedicated_support = Column(Boolean, default=False, server_default="false")
# Vigencia
starts_at = Column(DateTime(timezone=True), nullable=False)
@@ -85,10 +93,10 @@ class LicenseUsage(Base, TimestampMixin):
period_start = Column(DateTime(timezone=True), nullable=False)
period_end = Column(DateTime(timezone=True), nullable=False)
active_users = Column(Integer, default=0)
storage_used_gb = Column(Integer, default=0)
operations_count = Column(Integer, default=0)
api_calls_count = Column(Integer, default=0)
active_users = Column(Integer, default=0, server_default="0")
storage_used_gb = Column(Integer, default=0, server_default="0")
operations_count = Column(Integer, default=0, server_default="0")
api_calls_count = Column(Integer, default=0, server_default="0")
def __repr__(self):
return f"<LicenseUsage(tenant_id={self.tenant_id}, operations={self.operations_count})>"

View File

@@ -1,5 +1,6 @@
from .auth.routes import router as auth_router
from .licenses.routes import router as licenses_router
from .permissions.routes import router as permissions_router
from .tenants.routes import router as tenants_router
from .user_tenant.routes import router as user_tenant_router
from .users.routes import router as users_router
@@ -13,4 +14,5 @@ router.include_router(tenants_router, prefix="/core", tags=["core / tenants"])
router.include_router(user_tenant_router, prefix="/core", tags=["core / user-tenants"])
router.include_router(users_router, prefix="/core", tags=["core / users"])
router.include_router(licenses_router, prefix="/core", tags=["core / licenses"])
router.include_router(permissions_router, prefix="/core", tags=["core / permissions"])
router.include_router(dashboard_router, prefix="/core", tags=["core / dashboard"])

View File

@@ -37,7 +37,12 @@ class Tenant(Base, TimestampMixin):
slug = Column(String(100), unique=True, nullable=False, index=True)
# Tipo de tenant (compartido o dedicado)
type = Column(SQLEnum(TenantType), default=TenantType.SHARED, nullable=False)
type = Column(
SQLEnum(TenantType),
default=TenantType.SHARED,
server_default="SHARED",
nullable=False,
)
# Keycloak realm asociado
keycloak_realm = Column(String(255), nullable=False)
@@ -51,7 +56,7 @@ class Tenant(Base, TimestampMixin):
contact_phone = Column(String(50))
# Estado
is_active = Column(Boolean, default=True, nullable=False)
is_active = Column(Boolean, default=True, server_default="true", nullable=False)
# Relación con UserTenant
user_relations: Mapped[List["UserTenant"]] = relationship(

View File

@@ -45,7 +45,9 @@ class UserTenant(Base, TenantScopedMixin, TimestampMixin):
)
# Estado de la relación
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
is_active: Mapped[bool] = mapped_column(
Boolean, default=True, server_default="true", nullable=False
)
# Información adicional - Rol del usuario en este tenant (opcional)
role: Mapped[Optional[str]] = mapped_column(String(50), nullable=True)

View File

@@ -3,10 +3,10 @@ from core.database import Base
from sqlalchemy import Column, ForeignKeyConstraint, String
class TrailerType(Base, TenantScopedMixin, TimestampMixin):
class TrailerType(Base, TimestampMixin):
__tablename__ = "trailer_type"
__table_args__ = (
{"schema": "a76"},
{"schema": "public"},
)
trailer_type_key = Column(String(2), primary_key=True, nullable=False)

View File

@@ -4,6 +4,7 @@ Backend API con FastAPI + Keycloak + SQLAlchemy
"""
import logging
import subprocess
from api.v1.router import router as api_v1_router
from core.config import settings
@@ -75,12 +76,19 @@ async def http_exception_handler(request: Request, exc: HTTPException):
)
def run_migrations():
subprocess.run(
["alembic", "upgrade", "head"],
check=True
)
# Inicializar la base de datos
@app.on_event("startup")
async def on_startup():
"""Evento de inicio de la aplicación"""
logger.info("Iniciando la aplicación Anexo76...")
init_db()
run_migrations()
logger.info("Base de datos inicializada correctamente.")

View File

@@ -41,18 +41,18 @@ wait_for_tcp "${CORE_DB_HOST:-postgres-a76}" "${CORE_DB_PORT:-5432}" "PostgreSQL
wait_for_tcp "keycloak" "8080" "Keycloak"
# Ejecutar migraciones de Alembic
if [ -d "/app/alembic" ]; then
echo "Ejecutando migraciones de Alembic..."
alembic upgrade head || {
echo "⚠ WARNING: Error al ejecutar migraciones"
echo " Verificando estado de la base de datos..."
alembic current || echo " No se pudo determinar la versión actual"
}
echo "✓ Migraciones completadas"
else
echo "⚠ WARNING: Directorio /app/alembic no encontrado"
echo " Las migraciones de base de datos no se ejecutaron"
fi
#if [ -d "/app/alembic" ]; then
# echo "Ejecutando migraciones de Alembic..."
# alembic upgrade head || {
# echo "⚠ WARNING: Error al ejecutar migraciones"
# echo " Verificando estado de la base de datos..."
# alembic current || echo " No se pudo determinar la versión actual"
# }
# echo "✓ Migraciones completadas"
#else
# echo "⚠ WARNING: Directorio /app/alembic no encontrado"
# echo " Las migraciones de base de datos no se ejecutaron"
#fi
echo "=========================================="
echo "Iniciando aplicación FastAPI..."

View File

@@ -530,7 +530,7 @@ if [ $TENANT_QUERY_STATUS -ne 0 ]; then
echo -e "${RED}✗ Error al consultar el tenant:${NC}"
echo "$TENANT_ID_RESULT"
echo -e "${YELLOW}Verificando si la tabla existe...${NC}"
docker exec -e PGPASSWORD="${POSTGRES_PASSWORD}" anexo76-postgres-a76 psql -h localhost -p 5432 -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" -c "\dt core.tenants;"
exec_pg_sql "\dt core.tenants;"
exit 1
fi
@@ -540,7 +540,7 @@ if [ -z "$TENANT_ID" ]; then
echo -e "${RED}✗ Error: No se pudo obtener el ID del tenant${NC}"
echo "Resultado de la consulta: '$TENANT_ID_RESULT'"
echo -e "${YELLOW}Intentando ver todos los tenants...${NC}"
docker exec -e PGPASSWORD="${POSTGRES_PASSWORD}" anexo76-postgres-a76 psql -h localhost -p 5432 -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" -c "SELECT id, slug FROM core.tenants LIMIT 10;"
exec_pg_sql "SELECT id, slug FROM core.tenants LIMIT 10;"
exit 1
fi