From 4079a0cc0f246ae427aa6949158a11d65400d297 Mon Sep 17 00:00:00 2001 From: acazares Date: Wed, 14 Jan 2026 15:54:37 -0600 Subject: [PATCH] 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. --- ...31bf8cdae06_create_material_types_table.py | 333 ------------------ .../7937209f9718_seed_initial_data.py | 237 +++++++++---- backend/api/v1/common/base_models.py | 4 +- .../v1/modules/a24/fa/fa_classes/models.py | 28 +- .../v1/modules/a76/doc_types_dig/models.py | 11 +- .../modules/a76/general_catalogs/ports/dto.py | 2 +- .../a76/general_catalogs/ports/models.py | 2 +- .../units_of_measure/models.py | 91 +---- .../units_of_measure/seed_adua.py | 44 +-- .../units_of_measure/seed_med.py | 44 --- backend/api/v1/modules/a76/invoices/models.py | 77 ++-- backend/api/v1/modules/a76/parts/models.py | 4 +- .../a76/transportation/trailers/models.py | 2 +- .../api/v1/modules/core/licenses/models.py | 34 +- backend/api/v1/modules/core/router.py | 2 + backend/api/v1/modules/core/tenants/models.py | 9 +- .../api/v1/modules/core/user_tenant/models.py | 4 +- .../reference_data/trailer_types/models.py | 4 +- backend/main.py | 8 + scripts/backend-entrypoint.sh | 24 +- scripts/init_first_time.sh | 4 +- 21 files changed, 329 insertions(+), 639 deletions(-) delete mode 100644 backend/alembic/versions/531bf8cdae06_create_material_types_table.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/units_of_measure/seed_med.py diff --git a/backend/alembic/versions/531bf8cdae06_create_material_types_table.py b/backend/alembic/versions/531bf8cdae06_create_material_types_table.py deleted file mode 100644 index 7e7b4630..00000000 --- a/backend/alembic/versions/531bf8cdae06_create_material_types_table.py +++ /dev/null @@ -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 ### diff --git a/backend/alembic/versions/7937209f9718_seed_initial_data.py b/backend/alembic/versions/7937209f9718_seed_initial_data.py index a3ba52df..5ead8cc0 100644 --- a/backend/alembic/versions/7937209f9718_seed_initial_data.py +++ b/backend/alembic/versions/7937209f9718_seed_initial_data.py @@ -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") diff --git a/backend/api/v1/common/base_models.py b/backend/api/v1/common/base_models.py index ddfbafd6..78b26204 100644 --- a/backend/api/v1/common/base_models.py +++ b/backend/api/v1/common/base_models.py @@ -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) diff --git a/backend/api/v1/modules/a24/fa/fa_classes/models.py b/backend/api/v1/modules/a24/fa/fa_classes/models.py index 90c8cb1f..776baf54 100644 --- a/backend/api/v1/modules/a24/fa/fa_classes/models.py +++ b/backend/api/v1/modules/a24/fa/fa_classes/models.py @@ -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 diff --git a/backend/api/v1/modules/a76/doc_types_dig/models.py b/backend/api/v1/modules/a76/doc_types_dig/models.py index db7636ce..61b2efd0 100644 --- a/backend/api/v1/modules/a76/doc_types_dig/models.py +++ b/backend/api/v1/modules/a76/doc_types_dig/models.py @@ -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") diff --git a/backend/api/v1/modules/a76/general_catalogs/ports/dto.py b/backend/api/v1/modules/a76/general_catalogs/ports/dto.py index ebc91255..d173d9ea 100644 --- a/backend/api/v1/modules/a76/general_catalogs/ports/dto.py +++ b/backend/api/v1/modules/a76/general_catalogs/ports/dto.py @@ -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): diff --git a/backend/api/v1/modules/a76/general_catalogs/ports/models.py b/backend/api/v1/modules/a76/general_catalogs/ports/models.py index 01aa45df..61bdf8a4 100644 --- a/backend/api/v1/modules/a76/general_catalogs/ports/models.py +++ b/backend/api/v1/modules/a76/general_catalogs/ports/models.py @@ -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) diff --git a/backend/api/v1/modules/a76/general_catalogs/units_of_measure/models.py b/backend/api/v1/modules/a76/general_catalogs/units_of_measure/models.py index d33b82b6..4564c8d6 100644 --- a/backend/api/v1/modules/a76/general_catalogs/units_of_measure/models.py +++ b/backend/api/v1/modules/a76/general_catalogs/units_of_measure/models.py @@ -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( diff --git a/backend/api/v1/modules/a76/general_catalogs/units_of_measure/seed_adua.py b/backend/api/v1/modules/a76/general_catalogs/units_of_measure/seed_adua.py index 080b71e4..d0b91e84 100644 --- a/backend/api/v1/modules/a76/general_catalogs/units_of_measure/seed_adua.py +++ b/backend/api/v1/modules/a76/general_catalogs/units_of_measure/seed_adua.py @@ -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"), ] \ No newline at end of file diff --git a/backend/api/v1/modules/a76/general_catalogs/units_of_measure/seed_med.py b/backend/api/v1/modules/a76/general_catalogs/units_of_measure/seed_med.py deleted file mode 100644 index 75131b93..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/units_of_measure/seed_med.py +++ /dev/null @@ -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', ''), -] \ No newline at end of file diff --git a/backend/api/v1/modules/a76/invoices/models.py b/backend/api/v1/modules/a76/invoices/models.py index 1bc8f8a0..22bfa0da 100644 --- a/backend/api/v1/modules/a76/invoices/models.py +++ b/backend/api/v1/modules/a76/invoices/models.py @@ -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 diff --git a/backend/api/v1/modules/a76/parts/models.py b/backend/api/v1/modules/a76/parts/models.py index db56d294..b920065a 100644 --- a/backend/api/v1/modules/a76/parts/models.py +++ b/backend/api/v1/modules/a76/parts/models.py @@ -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() diff --git a/backend/api/v1/modules/a76/transportation/trailers/models.py b/backend/api/v1/modules/a76/transportation/trailers/models.py index 038c8987..d3b19e8b 100644 --- a/backend/api/v1/modules/a76/transportation/trailers/models.py +++ b/backend/api/v1/modules/a76/transportation/trailers/models.py @@ -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) diff --git a/backend/api/v1/modules/core/licenses/models.py b/backend/api/v1/modules/core/licenses/models.py index 2bf4bcb9..e08a4145 100644 --- a/backend/api/v1/modules/core/licenses/models.py +++ b/backend/api/v1/modules/core/licenses/models.py @@ -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"" diff --git a/backend/api/v1/modules/core/router.py b/backend/api/v1/modules/core/router.py index 3be9c11f..861eecaa 100644 --- a/backend/api/v1/modules/core/router.py +++ b/backend/api/v1/modules/core/router.py @@ -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"]) diff --git a/backend/api/v1/modules/core/tenants/models.py b/backend/api/v1/modules/core/tenants/models.py index e76fd34e..91eb9aff 100644 --- a/backend/api/v1/modules/core/tenants/models.py +++ b/backend/api/v1/modules/core/tenants/models.py @@ -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( diff --git a/backend/api/v1/modules/core/user_tenant/models.py b/backend/api/v1/modules/core/user_tenant/models.py index 9fe82c4d..2674b292 100644 --- a/backend/api/v1/modules/core/user_tenant/models.py +++ b/backend/api/v1/modules/core/user_tenant/models.py @@ -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) diff --git a/backend/api/v1/modules/public/reference_data/trailer_types/models.py b/backend/api/v1/modules/public/reference_data/trailer_types/models.py index f4cb7e8a..9a3698c9 100644 --- a/backend/api/v1/modules/public/reference_data/trailer_types/models.py +++ b/backend/api/v1/modules/public/reference_data/trailer_types/models.py @@ -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) diff --git a/backend/main.py b/backend/main.py index adf43bb0..b7ed6b69 100644 --- a/backend/main.py +++ b/backend/main.py @@ -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.") diff --git a/scripts/backend-entrypoint.sh b/scripts/backend-entrypoint.sh index d6f2eff1..20c5f89f 100755 --- a/scripts/backend-entrypoint.sh +++ b/scripts/backend-entrypoint.sh @@ -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..." diff --git a/scripts/init_first_time.sh b/scripts/init_first_time.sh index 31085dcf..e9416f4a 100755 --- a/scripts/init_first_time.sh +++ b/scripts/init_first_time.sh @@ -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