feat: Implement multi-tenancy support in middleware and security layers

- Enhanced TenantMiddleware to validate tenant information from JWT tokens.
- Added LicenseValidationMiddleware to check tenant licenses before processing requests.
- Updated security utilities to extract tenant information from tokens and validate company access.
- Introduced CompanyStore to manage active company state and handle company switching in the frontend.
- Modified API routes to include company_id in requests for better resource management.
- Improved logging and error handling throughout the middleware and API layers.
- Updated frontend components to reflect changes in company management and selection.
- Added new API route for fetching user's companies with proper authentication handling.
This commit is contained in:
2025-11-11 14:00:56 -06:00
parent e1eb6bbd01
commit 52b8fcd434
242 changed files with 7067 additions and 3274 deletions

View File

@@ -13,6 +13,7 @@ logger = logging.getLogger(__name__)
# access to the values within the .ini file in use.
config = context.config
def get_database_url():
"""Obtiene la URL de la base de datos (PostgreSQL) desde variables de entorno o alembic.ini."""
# Intentar construir desde variables de entorno primero
@@ -41,6 +42,7 @@ def get_database_url():
return url
# Configurar la URL de la base de datos
database_url = get_database_url()
@@ -77,6 +79,7 @@ config = context.config
fileConfig(config.config_file_name)
target_metadata = Base.metadata
def import_models_from_dir(dir_path: str):
"""Importa recursivamente cualquier archivo models.py desde dir_path y archivos en directorios models/"""
for root, dirs, files in os.walk(dir_path):
@@ -99,18 +102,20 @@ def import_models_from_dir(dir_path: str):
rel_path = os.path.relpath(module_path, BASE_DIR)
module_name = rel_path.replace(os.sep, ".").replace(".py", "")
try:
spec = importlib.util.spec_from_file_location(module_name, module_path)
spec = importlib.util.spec_from_file_location(
module_name, module_path
)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
except Exception as e:
logger.warning(f"No se pudo importar {module_path}: {e}")
# Importar todos los models dentro de api/v1/modules y api/v1/modules/uploads
modules_dir = os.path.join(BASE_DIR, "api", "v1", "modules")
import_models_from_dir(modules_dir)
def run_migrations_offline() -> None:
"""Run migrations in 'offline' mode.
@@ -147,10 +152,7 @@ def run_migrations_online() -> None:
)
with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=target_metadata
)
context.configure(connection=connection, target_metadata=target_metadata)
with context.begin_transaction():
context.run_migrations()

View File

@@ -5,6 +5,7 @@ Revises:
Create Date: 2025-10-19 18:23:39.613953
"""
from typing import Sequence, Union
from alembic import op
@@ -12,7 +13,7 @@ import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '531bf8cdae06'
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
@@ -21,124 +22,147 @@ 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(
"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_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_index(
"ak_country_ame", "countries", ["ame_key"], unique=True, 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(
"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_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(
"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('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(
"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('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.PrimaryKeyConstraint('key', name='invoice_types_pkey'),
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('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(
"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.PrimaryKeyConstraint("key", name="invoice_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(
"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('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(
"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_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(
"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('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(
"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('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(
"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('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(
"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_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(
"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('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(
"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('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'
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",
)
# ### end Alembic commands ###
@@ -146,22 +170,22 @@ def upgrade() -> None:
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
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')
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

@@ -5,148 +5,289 @@ Revises: 531bf8cdae06
Create Date: 2025-10-19 18:23:55.258800
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
from api.v1.modules.public.reference_data.pedimento_codes.seed import seed as pedimento_codes_seed
from api.v1.modules.public.reference_data.pedimento_regimens.seed import seed as pedimento_regimens_seed
from api.v1.modules.public.reference_data.code_pedimento_regimens.seed import seed as code_pedimento_regimens_seed
from api.v1.modules.public.reference_data.containers.seed import seed as container_types_seed
from api.v1.modules.public.reference_data.pedimento_codes.seed import (
seed as pedimento_codes_seed,
)
from api.v1.modules.public.reference_data.pedimento_regimens.seed import (
seed as pedimento_regimens_seed,
)
from api.v1.modules.public.reference_data.code_pedimento_regimens.seed import (
seed as code_pedimento_regimens_seed,
)
from api.v1.modules.public.reference_data.containers.seed import (
seed as container_types_seed,
)
from api.v1.modules.public.reference_data.countries.seed import seed as countries_seed
from api.v1.modules.public.reference_data.currency_types.seed import seed as currency_types_seed
from api.v1.modules.public.reference_data.customs_sections.seed import seed as customs_sections_seed
from api.v1.modules.public.reference_data.customs_warehouses.seed import seed as customs_warehouses_seed
from api.v1.modules.public.reference_data.currency_types.seed import (
seed as currency_types_seed,
)
from api.v1.modules.public.reference_data.customs_sections.seed import (
seed as customs_sections_seed,
)
from api.v1.modules.public.reference_data.customs_warehouses.seed import (
seed as customs_warehouses_seed,
)
from api.v1.modules.public.reference_data.incoterms.seed import seed as incoterms_seed
from api.v1.modules.public.reference_data.invoice_types.seed import seed as invoice_types_seed
from api.v1.modules.public.reference_data.material_types.seed import seed as material_types_seed
from api.v1.modules.public.reference_data.payment_methods.seed import seed as payment_methods_seed
from api.v1.modules.public.reference_data.invoice_types.seed import (
seed as invoice_types_seed,
)
from api.v1.modules.public.reference_data.material_types.seed import (
seed as material_types_seed,
)
from api.v1.modules.public.reference_data.payment_methods.seed import (
seed as payment_methods_seed,
)
from api.v1.modules.public.reference_data.sectors.seed import seed as sectors_seed
from api.v1.modules.public.reference_data.transport_modes.seed import seed as transport_modes_seed
from api.v1.modules.public.reference_data.transport_types.seed import seed as transport_types_seed
from api.v1.modules.public.reference_data.valuation_methods.seed import seed as valuation_methods_seed
from api.v1.modules.public.reference_data.transport_modes.seed import (
seed as transport_modes_seed,
)
from api.v1.modules.public.reference_data.transport_types.seed import (
seed as transport_types_seed,
)
from api.v1.modules.public.reference_data.valuation_methods.seed import (
seed as valuation_methods_seed,
)
# revision identifiers, used by Alembic.
revision: str = '7937209f9718'
down_revision: Union[str, Sequence[str], None] = '531bf8cdae06'
revision: str = "7937209f9718"
down_revision: Union[str, Sequence[str], None] = "531bf8cdae06"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = '531bf8cdae06'
depends_on: Union[str, Sequence[str], None] = "531bf8cdae06"
def upgrade() -> None:
"""Upgrade schema."""
#Seeds
values_pc = ", ".join([f"('{code}', '{desc.replace(chr(39), chr(39)*2)}')" for code, desc in pedimento_codes_seed])
op.execute(f"""
# Seeds
values_pc = ", ".join(
[
f"('{code}', '{desc.replace(chr(39), chr(39)*2)}')"
for code, desc in pedimento_codes_seed
]
)
op.execute(
f"""
INSERT INTO pedimento_codes (code, description) VALUES
{values_pc}
ON CONFLICT (code) DO NOTHING;
""")
"""
)
values_pr = ", ".join([f"('{code}', '{desc.replace(chr(39), chr(39)*2)}')" for code, desc in pedimento_regimens_seed])
op.execute(f"""
values_pr = ", ".join(
[
f"('{code}', '{desc.replace(chr(39), chr(39)*2)}')"
for code, desc in pedimento_regimens_seed
]
)
op.execute(
f"""
INSERT INTO public.pedimento_regimens (code, description) VALUES
{values_pr}
ON CONFLICT (code) DO NOTHING;
""")
"""
)
values_cpr = ", ".join([f"('{ped_code}', '{reg_code}', '{type_code}')" for ped_code, reg_code, type_code in code_pedimento_regimens_seed])
op.execute(f"""
values_cpr = ", ".join(
[
f"('{ped_code}', '{reg_code}', '{type_code}')"
for ped_code, reg_code, type_code in code_pedimento_regimens_seed
]
)
op.execute(
f"""
INSERT INTO public.code_pedimento_regimens (pedimento_code, regimen_code, type_code) VALUES
{values_cpr}
""")
"""
)
values_c = ", ".join([f"('{key}', '{desc.replace(chr(39), chr(39)*2)}')" for key, desc in container_types_seed])
op.execute(f"""
values_c = ", ".join(
[
f"('{key}', '{desc.replace(chr(39), chr(39)*2)}')"
for key, desc in container_types_seed
]
)
op.execute(
f"""
INSERT INTO public.containers (key, description) VALUES
{values_c}
ON CONFLICT (key) DO NOTHING;
""")
"""
)
values_country = ", ".join([f"('{m3_key}', '{mex_key}', '{ame_key}', '{desc_es.replace(chr(39), chr(39)*2)}', '{desc_en.replace(chr(39), chr(39)*2)}')" for m3_key, mex_key, ame_key, desc_es, desc_en in countries_seed])
op.execute(f"""
values_country = ", ".join(
[
f"('{m3_key}', '{mex_key}', '{ame_key}', '{desc_es.replace(chr(39), chr(39)*2)}', '{desc_en.replace(chr(39), chr(39)*2)}')"
for m3_key, mex_key, ame_key, desc_es, desc_en in countries_seed
]
)
op.execute(
f"""
INSERT INTO public.countries (m3_key, mex_key, ame_key, description_es, description_en) VALUES
{values_country}
ON CONFLICT (m3_key) DO NOTHING;
""")
"""
)
values_ct = ", ".join([f"('{code}', '{currency_name.replace(chr(39), chr(39)*2)}', '{country_desc.replace(chr(39), chr(39)*2)}')" for code, currency_name, country_desc in currency_types_seed])
op.execute(f"""
values_ct = ", ".join(
[
f"('{code}', '{currency_name.replace(chr(39), chr(39)*2)}', '{country_desc.replace(chr(39), chr(39)*2)}')"
for code, currency_name, country_desc in currency_types_seed
]
)
op.execute(
f"""
INSERT INTO public.currency_types (code, currency_name, country_description) VALUES
{values_ct}
ON CONFLICT (code) DO NOTHING;
""")
"""
)
values_cs = ", ".join([f"('{code}', '{name.replace(chr(39), chr(39)*2)}')" for code, name in customs_sections_seed])
op.execute(f"""
values_cs = ", ".join(
[
f"('{code}', '{name.replace(chr(39), chr(39)*2)}')"
for code, name in customs_sections_seed
]
)
op.execute(
f"""
INSERT INTO public.customs_sections (customs_code, section_name) VALUES
{values_cs}
ON CONFLICT (customs_code) DO NOTHING;
""")
"""
)
values_cw = ", ".join([f"('{key}', '{customs.replace(chr(39), chr(39)*2)}', '{fiscalized_warehouse.replace(chr(39), chr(39)*2)}')" for key, customs, fiscalized_warehouse in customs_warehouses_seed])
op.execute(f"""
values_cw = ", ".join(
[
f"('{key}', '{customs.replace(chr(39), chr(39)*2)}', '{fiscalized_warehouse.replace(chr(39), chr(39)*2)}')"
for key, customs, fiscalized_warehouse in customs_warehouses_seed
]
)
op.execute(
f"""
INSERT INTO public.customs_warehouses (key, customs, fiscalized_warehouse)
VALUES
{values_cw}
ON CONFLICT (key, customs) DO NOTHING;
""")
"""
)
values_incoterms = ", ".join([f"('{code}', '{desc_es.replace(chr(39), chr(39)*2)}', '{desc_en.replace(chr(39), chr(39)*2)}')" for code, desc_es, desc_en in incoterms_seed])
op.execute(f"""
values_incoterms = ", ".join(
[
f"('{code}', '{desc_es.replace(chr(39), chr(39)*2)}', '{desc_en.replace(chr(39), chr(39)*2)}')"
for code, desc_es, desc_en in incoterms_seed
]
)
op.execute(
f"""
INSERT INTO public.incoterms (code, description_es, description_en) VALUES
{values_incoterms}
ON CONFLICT (code) DO NOTHING;
""")
"""
)
values_it = ", ".join([f"('{key}', '{desc.replace(chr(39), chr(39)*2)}', '{note.replace(chr(39), chr(39)*2)}', '{type.replace(chr(39), chr(39)*2)}')" for key, desc, note, type in invoice_types_seed])
op.execute(f"""
values_it = ", ".join(
[
f"('{key}', '{desc.replace(chr(39), chr(39)*2)}', '{note.replace(chr(39), chr(39)*2)}', '{type.replace(chr(39), chr(39)*2)}')"
for key, desc, note, type in invoice_types_seed
]
)
op.execute(
f"""
INSERT INTO public.invoice_types (key, description, note, type) VALUES
{values_it}
ON CONFLICT (key) DO NOTHING;
""")
"""
)
values_mt = ", ".join([f"('{key}', '{desc.replace(chr(39), chr(39)*2)}', '{category.replace(chr(39), chr(39)*2)}')" for key, desc, category in material_types_seed])
op.execute(f"""
values_mt = ", ".join(
[
f"('{key}', '{desc.replace(chr(39), chr(39)*2)}', '{category.replace(chr(39), chr(39)*2)}')"
for key, desc, category in material_types_seed
]
)
op.execute(
f"""
INSERT INTO public.material_types (key, description, type) VALUES
{values_mt}
ON CONFLICT (key) DO NOTHING;
""")
"""
)
values_pm = ", ".join([f"('{key}', '{desc.replace(chr(39), chr(39)*2)}')" for key, desc in payment_methods_seed])
op.execute(f"""
values_pm = ", ".join(
[
f"('{key}', '{desc.replace(chr(39), chr(39)*2)}')"
for key, desc in payment_methods_seed
]
)
op.execute(
f"""
INSERT INTO public.payment_methods (key, description) VALUES
{values_pm}
ON CONFLICT (key) DO NOTHING;
""")
"""
)
values_sectors = ", ".join([f"('{key}', '{desc.replace(chr(39), chr(39)*2)}', '{authorized}')" for key, desc, authorized in sectors_seed])
op.execute(f"""
values_sectors = ", ".join(
[
f"('{key}', '{desc.replace(chr(39), chr(39)*2)}', '{authorized}')"
for key, desc, authorized in sectors_seed
]
)
op.execute(
f"""
INSERT INTO public.sectors (key, description, authorized) VALUES
{values_sectors}
ON CONFLICT (key) DO NOTHING;
""")
"""
)
values_tm = ", ".join([f"('{key}', '{name.replace(chr(39), chr(39)*2)}')" for key, name in transport_modes_seed])
op.execute(f"""
values_tm = ", ".join(
[
f"('{key}', '{name.replace(chr(39), chr(39)*2)}')"
for key, name in transport_modes_seed
]
)
op.execute(
f"""
INSERT INTO public.transport_modes (key, name) VALUES
{values_tm}
ON CONFLICT (key) DO NOTHING;
""")
"""
)
values_tt = ", ".join([f"('{code}', '{desc.replace(chr(39), chr(39)*2)}')" for code, desc in transport_types_seed])
op.execute(f"""
values_tt = ", ".join(
[
f"('{code}', '{desc.replace(chr(39), chr(39)*2)}')"
for code, desc in transport_types_seed
]
)
op.execute(
f"""
INSERT INTO public.transport_types (transport_code, description) VALUES
{values_tt}
ON CONFLICT (transport_code) DO NOTHING;
""")
"""
)
values_vm = ", ".join([f"('{key}', '{desc.replace(chr(39), chr(39)*2)}')" for key, desc in valuation_methods_seed])
op.execute(f"""
values_vm = ", ".join(
[
f"('{key}', '{desc.replace(chr(39), chr(39)*2)}')"
for key, desc in valuation_methods_seed
]
)
op.execute(
f"""
INSERT INTO public.valuation_methods (key, description) VALUES
{values_vm}
ON CONFLICT (key) DO NOTHING;
""")
"""
)
def downgrade() -> None:
"""Downgrade schema."""

View File

@@ -1,18 +1,30 @@
from decimal import Decimal
from sqlalchemy import Boolean, ForeignKeyConstraint, Integer, Numeric, PrimaryKeyConstraint, String
from sqlalchemy import (
Boolean,
ForeignKeyConstraint,
Integer,
Numeric,
PrimaryKeyConstraint,
String,
)
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy.orm.base import Mapped
from core.database import Base
class QClasses(Base):
__tablename__ = 'q_classes' #QClases
__tablename__ = "q_classes" # QClases
__table_args__ = (
PrimaryKeyConstraint('id', name='qclases_pk'),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_qclasses_tenants'),
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_qclasses_company'),
ForeignKeyConstraint(['class_id'], ['classes.id'], name='fk_qclasses_classes'),
{'schema': 'a24'}
PrimaryKeyConstraint("id", name="qclases_pk"),
ForeignKeyConstraint(
["tenant_id"], ["a76.tenants.id"], name="fk_qclasses_tenants"
),
ForeignKeyConstraint(
["company_id"], ["a76.company.id"], name="fk_qclasses_company"
),
ForeignKeyConstraint(["class_id"], ["classes.id"], name="fk_qclasses_classes"),
{"schema": "a24"},
)
id: Mapped[int] = mapped_column(Integer, primary_key=True)
@@ -20,13 +32,11 @@ class QClasses(Base):
tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
company_id: Mapped[int] = mapped_column(Integer, nullable=True, index=True)
import_tariff_code: Mapped[str] = mapped_column(String(10)) #FRACCIONIMPO
import_tariff_type: Mapped[str] = mapped_column(String(6)) #TIPOFRACIMPO
export_tariff_code: Mapped[str] = mapped_column(String(10)) #FRACCIONEXPO
export_tariff_type: Mapped[str] = mapped_column(String(6)) #TIPOFRACEXPO
depreciation_rate: Mapped[Decimal] = mapped_column(Numeric(5, 2)) #TASADEPRECIA
fda_code: Mapped[str] = mapped_column(String(20)) #FDA
eccn_code: Mapped[str] = mapped_column(String(20)) #ECCN
class_enabled: Mapped[bool] = mapped_column(Boolean) #HABILITADESHABILITACLASE
import_tariff_code: Mapped[str] = mapped_column(String(10)) # FRACCIONIMPO
import_tariff_type: Mapped[str] = mapped_column(String(6)) # TIPOFRACIMPO
export_tariff_code: Mapped[str] = mapped_column(String(10)) # FRACCIONEXPO
export_tariff_type: Mapped[str] = mapped_column(String(6)) # TIPOFRACEXPO
depreciation_rate: Mapped[Decimal] = mapped_column(Numeric(5, 2)) # TASADEPRECIA
fda_code: Mapped[str] = mapped_column(String(20)) # FDA
eccn_code: Mapped[str] = mapped_column(String(20)) # ECCN
class_enabled: Mapped[bool] = mapped_column(Boolean) # HABILITADESHABILITACLASE

View File

@@ -1,21 +1,34 @@
from sqlalchemy import ForeignKeyConstraint, Integer, PrimaryKeyConstraint, String, UniqueConstraint
from sqlalchemy import (
ForeignKeyConstraint,
Integer,
PrimaryKeyConstraint,
String,
UniqueConstraint,
)
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy.orm.base import Mapped
from core.database import Base
class SClasses(Base):
__tablename__ = 's_classes' #SClases
__tablename__ = "s_classes" # SClases
__table_args__ = (
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_sclasses_tenants'),
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_sclasses_company'),
ForeignKeyConstraint(['class_id'], ['a76.clases.class_id'], name='fk_sclasses_classes'),
PrimaryKeyConstraint('id', name='sclases_pk'),
{'schema': 'a24'}
ForeignKeyConstraint(
["tenant_id"], ["a76.tenants.id"], name="fk_sclasses_tenants"
),
ForeignKeyConstraint(
["company_id"], ["a76.company.id"], name="fk_sclasses_company"
),
ForeignKeyConstraint(
["class_id"], ["a76.clases.class_id"], name="fk_sclasses_classes"
),
PrimaryKeyConstraint("id", name="sclases_pk"),
{"schema": "a24"},
)
id: Mapped[int] = mapped_column(Integer, primary_key=True)
class_id: Mapped[int] = mapped_column(Integer, nullable=False) #Id de clase
class_id: Mapped[int] = mapped_column(Integer, nullable=False) # Id de clase
tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
company_id: Mapped[int] = mapped_column(Integer, nullable=True, index=True)
stock_um: Mapped[str] = mapped_column(String(5)) #Unidad de medida para existencia
us_tariff_code: Mapped[str] = mapped_column(String(19)) #Fracción americana (USA)
stock_um: Mapped[str] = mapped_column(String(5)) # Unidad de medida para existencia
us_tariff_code: Mapped[str] = mapped_column(String(19)) # Fracción americana (USA)

View File

@@ -1,6 +1,7 @@
"""
Módulo de Authentication
"""
from .routes import router
__all__ = ["router"]

View File

@@ -1,12 +1,14 @@
"""
DTOs para módulo de autenticación
"""
from pydantic import BaseModel, EmailStr, Field
from typing import Optional
class LoginRequestDTO(BaseModel):
"""DTO para solicitud de login"""
username: str = Field(..., description="Usuario o email")
password: str = Field(..., min_length=6, description="Contraseña")
tenant_slug: str = Field(..., description="Slug del tenant")
@@ -16,13 +18,14 @@ class LoginRequestDTO(BaseModel):
"example": {
"username": "usuario@ejemplo.com",
"password": "password123",
"tenant_slug": "empresa-abc"
"tenant_slug": "empresa-abc",
}
}
class TokenResponseDTO(BaseModel):
"""DTO para respuesta de token"""
access_token: str
refresh_token: str
token_type: str = "bearer"
@@ -34,18 +37,20 @@ class TokenResponseDTO(BaseModel):
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer",
"expires_in": 3600
"expires_in": 3600,
}
}
class RefreshTokenRequestDTO(BaseModel):
"""DTO para solicitud de refresh token"""
refresh_token: str = Field(..., description="Refresh token")
class UserInfoResponseDTO(BaseModel):
"""DTO para información de usuario"""
sub: str
email: Optional[str] = None
name: Optional[str] = None
@@ -61,19 +66,23 @@ class UserInfoResponseDTO(BaseModel):
"name": "Juan Pérez",
"preferred_username": "jperez",
"tenant_id": 1,
"roles": ["user", "admin"]
"roles": ["user", "admin"],
}
}
class LogoutRequestDTO(BaseModel):
"""DTO para solicitud de logout"""
refresh_token: str = Field(..., description="Refresh token para invalidar")
class RegisterRequestDTO(BaseModel):
"""DTO para solicitud de registro"""
username: str = Field(..., min_length=3, max_length=50, description="Nombre de usuario")
username: str = Field(
..., min_length=3, max_length=50, description="Nombre de usuario"
)
email: EmailStr = Field(..., description="Email del usuario")
password: str = Field(..., min_length=8, description="Contraseña")
first_name: str = Field(..., min_length=2, max_length=50, description="Nombre")
@@ -88,13 +97,14 @@ class RegisterRequestDTO(BaseModel):
"password": "MiPassword123!",
"first_name": "Juan",
"last_name": "Pérez",
"tenant_slug": "empresa-abc"
"tenant_slug": "empresa-abc",
}
}
class RegisterResponseDTO(BaseModel):
"""DTO para respuesta de registro"""
user_id: str
username: str
email: str
@@ -106,13 +116,14 @@ class RegisterResponseDTO(BaseModel):
"user_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"username": "jperez",
"email": "jperez@ejemplo.com",
"message": "User registered successfully"
"message": "User registered successfully",
}
}
class ExchangeCodeRequestDTO(BaseModel):
"""DTO para intercambiar authorization code por tokens (OAuth2 flow)"""
code: str = Field(..., description="Authorization code de OAuth2")
redirect_uri: str = Field(..., description="Redirect URI usado en la autorización")
tenant_slug: Optional[str] = Field(None, description="Slug del tenant (opcional)")
@@ -122,13 +133,14 @@ class ExchangeCodeRequestDTO(BaseModel):
"example": {
"code": "eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Ii...",
"redirect_uri": "http://localhost:5173/auth/callback",
"tenant_slug": "empresa-abc"
"tenant_slug": "empresa-abc",
}
}
class SetCookieRequestDTO(BaseModel):
"""DTO para establecer cookies de autenticación"""
access_token: str = Field(..., description="Access token JWT")
refresh_token: str = Field(..., description="Refresh token JWT")
@@ -136,6 +148,6 @@ class SetCookieRequestDTO(BaseModel):
json_schema_extra = {
"example": {
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
"refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
}
}

View File

@@ -1,6 +1,7 @@
"""
Endpoints API para autenticación
"""
from fastapi import APIRouter, Depends, HTTPException, Response
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from sqlalchemy.orm import Session
@@ -16,7 +17,7 @@ from .dto import (
RegisterRequestDTO,
RegisterResponseDTO,
ExchangeCodeRequestDTO,
SetCookieRequestDTO
SetCookieRequestDTO,
)
from .service import AuthService
@@ -26,8 +27,7 @@ security = HTTPBearer()
@router.post("/register", response_model=RegisterResponseDTO, status_code=201)
async def register(
register_data: RegisterRequestDTO,
db: Session = Depends(get_core_db)
register_data: RegisterRequestDTO, db: Session = Depends(get_core_db)
):
"""
Registra un nuevo usuario en Keycloak
@@ -50,10 +50,7 @@ async def register(
@router.post("/login", response_model=TokenResponseDTO)
async def login(
login_data: LoginRequestDTO,
db: Session = Depends(get_core_db)
):
async def login(login_data: LoginRequestDTO, db: Session = Depends(get_core_db)):
"""
Autentica usuario con Keycloak y retorna tokens JWT
@@ -68,8 +65,7 @@ async def login(
@router.post("/refresh", response_model=TokenResponseDTO)
async def refresh_token(
refresh_data: RefreshTokenRequestDTO,
db: Session = Depends(get_core_db)
refresh_data: RefreshTokenRequestDTO, db: Session = Depends(get_core_db)
):
"""
Refresca el access token usando el refresh token
@@ -81,7 +77,7 @@ async def refresh_token(
@router.get("/me", response_model=UserInfoResponseDTO)
async def get_current_user_info(
credentials: HTTPAuthorizationCredentials = Depends(security),
db: Session = Depends(get_core_db)
db: Session = Depends(get_core_db),
):
"""
Obtiene información del usuario actual desde el token
@@ -94,7 +90,7 @@ async def get_current_user_info(
async def logout(
logout_data: LogoutRequestDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Cierra sesión invalidando el refresh token
@@ -105,8 +101,7 @@ async def logout(
@router.post("/exchange-code", response_model=TokenResponseDTO)
async def exchange_code(
exchange_data: ExchangeCodeRequestDTO,
db: Session = Depends(get_core_db)
exchange_data: ExchangeCodeRequestDTO, db: Session = Depends(get_core_db)
):
"""
Intercambia un authorization code de OAuth2 por tokens
@@ -125,7 +120,7 @@ async def exchange_code(
async def set_cookie(
cookie_data: SetCookieRequestDTO,
response: Response,
db: Session = Depends(get_core_db)
db: Session = Depends(get_core_db),
):
"""
Establece cookies HttpOnly con los tokens de autenticación
@@ -155,7 +150,7 @@ async def set_cookie(
secure=False, # TODO: Cambiar a True en producción con HTTPS
samesite="lax", # Protección CSRF
max_age=3600, # 1 hora (ajustar según configuración del token)
path="/"
path="/",
)
# Refresh token cookie
@@ -166,17 +161,14 @@ async def set_cookie(
secure=False, # TODO: Cambiar a True en producción con HTTPS
samesite="lax",
max_age=86400, # 24 horas (ajustar según configuración del token)
path="/"
path="/",
)
return {
"success": True,
"message": "Cookies establecidas correctamente",
"user": user_info
"user": user_info,
}
except Exception as e:
raise HTTPException(
status_code=400,
detail=f"Error validando tokens: {str(e)}"
)
raise HTTPException(status_code=400, detail=f"Error validando tokens: {str(e)}")

View File

@@ -1,6 +1,7 @@
"""
Servicio de autenticación con Keycloak
"""
from keycloak import KeycloakOpenID, KeycloakAdmin
from keycloak.exceptions import KeycloakError
from fastapi import HTTPException
@@ -15,7 +16,7 @@ from .dto import (
UserInfoResponseDTO,
LogoutRequestDTO,
RegisterRequestDTO,
RegisterResponseDTO
RegisterResponseDTO,
)
logger = logging.getLogger(__name__)
@@ -30,7 +31,7 @@ class AuthService:
server_url=settings.KEYCLOAK_SERVER_URL,
client_id=settings.KEYCLOAK_CLIENT_ID,
realm_name=settings.KEYCLOAK_REALM,
client_secret_key=settings.KEYCLOAK_CLIENT_SECRET
client_secret_key=settings.KEYCLOAK_CLIENT_SECRET,
)
def login(self, login_data: LoginRequestDTO) -> TokenResponseDTO:
@@ -66,14 +67,14 @@ class AuthService:
server_url=settings.KEYCLOAK_SERVER_URL,
client_id=settings.KEYCLOAK_CLIENT_ID,
realm_name=tenant.keycloak_realm,
client_secret_key=settings.KEYCLOAK_CLIENT_SECRET
client_secret_key=settings.KEYCLOAK_CLIENT_SECRET,
)
# Obtener token de Keycloak
token_response = keycloak_client.token(
username=login_data.username,
password=login_data.password,
grant_type=["password"]
grant_type=["password"],
)
# Obtener información del usuario y verificar acceso al tenant
@@ -82,13 +83,16 @@ class AuthService:
if user_id:
# Verificar si el usuario tiene acceso a este tenant
has_access = user_tenant_service.user_has_access_to_tenant(user_id, tenant.id)
has_access = user_tenant_service.user_has_access_to_tenant(
user_id, tenant.id
)
if not has_access:
logger.warning(f"User {user_id} tried to access tenant {tenant.id} without permission")
logger.warning(
f"User {user_id} tried to access tenant {tenant.id} without permission"
)
raise HTTPException(
status_code=403,
detail="You don't have access to this tenant"
status_code=403, detail="You don't have access to this tenant"
)
# Actualizar el tenant_id del usuario en Keycloak basado en el slug usado
@@ -100,7 +104,7 @@ class AuthService:
password=settings.KEYCLOAK_ADMIN_PASSWORD,
realm_name=tenant.keycloak_realm,
user_realm_name="master",
verify=True
verify=True,
)
# Obtener los datos actuales del usuario para no sobrescribirlos
@@ -120,11 +124,13 @@ class AuthService:
"lastName": current_user.get("lastName"),
"enabled": current_user.get("enabled", True),
"emailVerified": current_user.get("emailVerified", False),
"attributes": current_attributes
"attributes": current_attributes,
}
keycloak_admin.update_user(user_id=user_id, payload=update_payload)
logger.info(f"Updated tenant_id={tenant.id} for user {login_data.username}")
logger.info(
f"Updated tenant_id={tenant.id} for user {login_data.username}"
)
except Exception as e:
# No queremos que falle el login si no se puede actualizar el atributo
@@ -134,7 +140,7 @@ class AuthService:
access_token=token_response["access_token"],
refresh_token=token_response["refresh_token"],
token_type="bearer",
expires_in=token_response["expires_in"]
expires_in=token_response["expires_in"],
)
except KeycloakError as e:
@@ -165,12 +171,14 @@ class AuthService:
access_token=token_response["access_token"],
refresh_token=token_response["refresh_token"],
token_type="bearer",
expires_in=token_response["expires_in"]
expires_in=token_response["expires_in"],
)
except KeycloakError as e:
logger.warning(f"Token refresh failed: {str(e)}")
raise HTTPException(status_code=401, detail="Invalid or expired refresh token")
raise HTTPException(
status_code=401, detail="Invalid or expired refresh token"
)
except Exception as e:
logger.error(f"Token refresh error: {str(e)}")
raise HTTPException(status_code=500, detail="Token refresh error")
@@ -204,7 +212,7 @@ class AuthService:
name=user_info.get("name"),
preferred_username=user_info.get("preferred_username"),
tenant_id=int(tenant_id) if tenant_id else None,
roles=roles
roles=roles,
)
except KeycloakError as e:
@@ -253,6 +261,7 @@ class AuthService:
try:
# Verificar que el tenant existe
from api.v1.modules.a76.tenants.service import TenantService
tenant_service = TenantService(self.db)
tenant = tenant_service.get_tenant_by_slug(register_data.tenant_slug)
@@ -269,7 +278,7 @@ class AuthService:
password=settings.KEYCLOAK_ADMIN_PASSWORD,
realm_name=tenant.keycloak_realm,
user_realm_name="master", # El admin suele estar en master realm
verify=True
verify=True,
)
# Preparar datos del usuario para Keycloak
@@ -280,15 +289,14 @@ class AuthService:
"lastName": register_data.last_name,
"enabled": True,
"emailVerified": False,
"credentials": [{
"type": "password",
"value": register_data.password,
"temporary": False
}],
"attributes": {
"tenant_id": str(tenant.id),
"tenant_slug": tenant.slug
}
"credentials": [
{
"type": "password",
"value": register_data.password,
"temporary": False,
}
],
"attributes": {"tenant_id": str(tenant.id), "tenant_slug": tenant.slug},
}
# Crear usuario en Keycloak
@@ -307,11 +315,12 @@ class AuthService:
# Agregar el usuario al tenant en la base de datos
try:
from api.v1.modules.a76.user_tenant.service import UserTenantService
user_tenant_service = UserTenantService(self.db)
user_tenant_service.add_user_to_tenant(
keycloak_user_id=user_id,
tenant_id=tenant.id,
role="user" # Rol por defecto
role="user", # Rol por defecto
)
logger.info(f"Added user {user_id} to tenant {tenant.id} in database")
except Exception as e:
@@ -323,17 +332,18 @@ class AuthService:
except:
pass
raise HTTPException(
status_code=500,
detail="Failed to register user in database"
status_code=500, detail="Failed to register user in database"
)
logger.info(f"User registered: {register_data.username} (tenant: {tenant.slug}, user_id: {user_id})")
logger.info(
f"User registered: {register_data.username} (tenant: {tenant.slug}, user_id: {user_id})"
)
return RegisterResponseDTO(
user_id=user_id,
username=register_data.username,
email=register_data.email,
message="User registered successfully"
message="User registered successfully",
)
except KeycloakError as e:
@@ -342,7 +352,9 @@ class AuthService:
# Mensajes de error más específicos
if "User exists" in error_message or "409" in error_message:
raise HTTPException(status_code=409, detail="Username or email already exists")
raise HTTPException(
status_code=409, detail="Username or email already exists"
)
elif "Invalid" in error_message:
raise HTTPException(status_code=400, detail="Invalid user data")
else:
@@ -377,9 +389,9 @@ class AuthService:
# Intercambiar código por tokens usando Keycloak
token_response = self.keycloak_openid.token(
grant_type='authorization_code',
grant_type="authorization_code",
code=exchange_data.code,
redirect_uri=exchange_data.redirect_uri
redirect_uri=exchange_data.redirect_uri,
)
logger.info(f"Code exchanged successfully")
@@ -388,11 +400,14 @@ class AuthService:
# Por ahora simplemente retornamos los tokens
if exchange_data.tenant_slug:
# Decodificar token para obtener tenant_id del usuario
user_info = self.keycloak_openid.introspect(token_response['access_token'])
user_tenant_id = user_info.get('tenant_id')
user_info = self.keycloak_openid.introspect(
token_response["access_token"]
)
user_tenant_id = user_info.get("tenant_id")
# Validar que el tenant existe y está activo
from api.v1.modules.a76.tenants.service import TenantService
tenant_service = TenantService(self.db)
tenant = tenant_service.get_tenant_by_slug(exchange_data.tenant_slug)
@@ -406,10 +421,10 @@ class AuthService:
# Esto depende de cómo manejes los tenants en tu aplicación
return TokenResponseDTO(
access_token=token_response['access_token'],
refresh_token=token_response['refresh_token'],
token_type=token_response.get('token_type', 'bearer'),
expires_in=token_response.get('expires_in', 3600)
access_token=token_response["access_token"],
refresh_token=token_response["refresh_token"],
token_type=token_response.get("token_type", "bearer"),
expires_in=token_response.get("expires_in", 3600),
)
except KeycloakError as e:
@@ -417,9 +432,13 @@ class AuthService:
logger.warning(f"Code exchange failed: {error_message}")
if "invalid_grant" in error_message.lower():
raise HTTPException(status_code=400, detail="Invalid or expired authorization code")
raise HTTPException(
status_code=400, detail="Invalid or expired authorization code"
)
elif "invalid_client" in error_message.lower():
raise HTTPException(status_code=401, detail="Invalid client credentials")
raise HTTPException(
status_code=401, detail="Invalid client credentials"
)
else:
raise HTTPException(status_code=500, detail="Token exchange error")

View File

@@ -1,6 +1,7 @@
"""
Módulo de Class
"""
from .routes import router
__all__ = ["router"]

View File

@@ -2,6 +2,7 @@
DTOs (Data Transfer Objects) para módulo de clases SCAII y SCAF
Reemplaza schemas.py siguiendo enfoque DDD y estilo NestJS
"""
from pydantic import BaseModel, Field
from typing import Optional
from datetime import datetime
@@ -9,17 +10,38 @@ from datetime import datetime
class ClassCreateDTO(BaseModel):
"""DTO para crear una clase"""
client_id: int = Field(..., description="Client key")
class_code: str = Field(..., max_length=8, description="Class code")
description_spanish: Optional[str] = Field(None, max_length=500, description="Description in Spanish")
description_english: Optional[str] = Field(None, max_length=500, description="Description in English")
material_key: Optional[str] = Field(None, max_length=10, description="Material key (homologated TIPOMAT/TIPOMATEQUIPO)")
unit_of_measure: Optional[str] = Field(None, max_length=5, description="Unit of measure (homologated UNIMEDIDA)")
fraction: Optional[str] = Field(None, max_length=10, description="Mexican tariff fraction")
us_fraction: Optional[str] = Field(None, max_length=16, description="US tariff fraction")
sub_key: Optional[str] = Field(None, max_length=5, description="Sub classification key")
physical_review: Optional[int] = Field(None, description="Physical review indicator")
iva_exempt_fraction: Optional[str] = Field(None, max_length=4, description="IVA exempt fraction")
description_spanish: Optional[str] = Field(
None, max_length=500, description="Description in Spanish"
)
description_english: Optional[str] = Field(
None, max_length=500, description="Description in English"
)
material_key: Optional[str] = Field(
None,
max_length=10,
description="Material key (homologated TIPOMAT/TIPOMATEQUIPO)",
)
unit_of_measure: Optional[str] = Field(
None, max_length=5, description="Unit of measure (homologated UNIMEDIDA)"
)
fraction: Optional[str] = Field(
None, max_length=10, description="Mexican tariff fraction"
)
us_fraction: Optional[str] = Field(
None, max_length=16, description="US tariff fraction"
)
sub_key: Optional[str] = Field(
None, max_length=5, description="Sub classification key"
)
physical_review: Optional[int] = Field(
None, description="Physical review indicator"
)
iva_exempt_fraction: Optional[str] = Field(
None, max_length=4, description="IVA exempt fraction"
)
class Config:
from_attributes = True
@@ -27,15 +49,36 @@ class ClassCreateDTO(BaseModel):
class ClassUpdateDTO(BaseModel):
"""DTO para actualizar una clase"""
description_spanish: Optional[str] = Field(None, max_length=500, description="Description in Spanish")
description_english: Optional[str] = Field(None, max_length=500, description="Description in English")
material_key: Optional[str] = Field(None, max_length=10, description="Material key (homologated TIPOMAT/TIPOMATEQUIPO)")
unit_of_measure: Optional[str] = Field(None, max_length=5, description="Unit of measure (homologated UNIMEDIDA)")
fraction: Optional[str] = Field(None, max_length=10, description="Mexican tariff fraction")
us_fraction: Optional[str] = Field(None, max_length=16, description="US tariff fraction")
sub_key: Optional[str] = Field(None, max_length=5, description="Sub classification key")
physical_review: Optional[int] = Field(None, description="Physical review indicator")
iva_exempt_fraction: Optional[str] = Field(None, max_length=4, description="IVA exempt fraction")
description_spanish: Optional[str] = Field(
None, max_length=500, description="Description in Spanish"
)
description_english: Optional[str] = Field(
None, max_length=500, description="Description in English"
)
material_key: Optional[str] = Field(
None,
max_length=10,
description="Material key (homologated TIPOMAT/TIPOMATEQUIPO)",
)
unit_of_measure: Optional[str] = Field(
None, max_length=5, description="Unit of measure (homologated UNIMEDIDA)"
)
fraction: Optional[str] = Field(
None, max_length=10, description="Mexican tariff fraction"
)
us_fraction: Optional[str] = Field(
None, max_length=16, description="US tariff fraction"
)
sub_key: Optional[str] = Field(
None, max_length=5, description="Sub classification key"
)
physical_review: Optional[int] = Field(
None, description="Physical review indicator"
)
iva_exempt_fraction: Optional[str] = Field(
None, max_length=4, description="IVA exempt fraction"
)
class Config:
from_attributes = True
@@ -43,6 +86,7 @@ class ClassUpdateDTO(BaseModel):
class ClassResponseDTO(BaseModel):
"""DTO para respuesta de clase"""
client_id: int
class_code: str
description_spanish: Optional[str] = None
@@ -61,6 +105,7 @@ class ClassResponseDTO(BaseModel):
class ClassBasicDTO(BaseModel):
"""DTO para información básica de clase"""
client_id: int
class_code: str
description_spanish: Optional[str] = None
@@ -74,6 +119,7 @@ class ClassBasicDTO(BaseModel):
class ClassListDTO(BaseModel):
"""DTO para lista de clases"""
classes: list[ClassBasicDTO]
total: int
page: int
@@ -85,13 +131,15 @@ class ClassListDTO(BaseModel):
class ClassSearchDTO(BaseModel):
"""DTO para búsqueda de clases"""
client_id: Optional[int] = Field(None, description="Filter by client key")
class_code: Optional[str] = Field(None, description="Search by class code")
description: Optional[str] = Field(None, description="Search in descriptions")
material_key: Optional[str] = Field(None, description="Filter by material key")
fraction: Optional[str] = Field(None, description="Filter by tariff fraction")
physical_review: Optional[int] = Field(None, description="Filter by physical review indicator")
physical_review: Optional[int] = Field(
None, description="Filter by physical review indicator"
)
class Config:
from_attributes = True

View File

@@ -1,8 +1,17 @@
"""
Modelos ORM para gestión de clases SCAII y SCAF
"""
from typing import TYPE_CHECKING, Optional
from sqlalchemy import Integer, String, SmallInteger, ForeignKey, PrimaryKeyConstraint, ForeignKeyConstraint, UniqueConstraint
from sqlalchemy import (
Integer,
String,
SmallInteger,
ForeignKey,
PrimaryKeyConstraint,
ForeignKeyConstraint,
UniqueConstraint,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
from core.database import Base
@@ -15,15 +24,31 @@ class Class(Base):
"""
Modelo para la tabla GClases - Información de clases en sistemas SCAII y SCAF
"""
__tablename__ = "classes"
__table_args__ = (
PrimaryKeyConstraint('id', name='classes_pkey'),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_classes_tenant'),
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_classes_company'),
ForeignKeyConstraint(['client_id'], ['a76.client_provider.id'], name='fk_classes_client'),
ForeignKeyConstraint(['material_key'], ['public.material_types.key'], name='fk_classes_material_type'),
UniqueConstraint('tenant_id', 'company_id', 'class_code', name='uq_classes_client_id_class_code'),
{"schema": "a76"}
PrimaryKeyConstraint("id", name="classes_pkey"),
ForeignKeyConstraint(
["tenant_id"], ["a76.tenants.id"], name="fk_classes_tenant"
),
ForeignKeyConstraint(
["company_id"], ["a76.company.id"], name="fk_classes_company"
),
ForeignKeyConstraint(
["client_id"], ["a76.client_provider.id"], name="fk_classes_client"
),
ForeignKeyConstraint(
["material_key"],
["public.material_types.key"],
name="fk_classes_material_type",
),
UniqueConstraint(
"tenant_id",
"company_id",
"class_code",
name="uq_classes_client_id_class_code",
),
{"schema": "a76"},
)
id: Mapped[int] = mapped_column(Integer, primary_key=True)
@@ -32,37 +57,45 @@ class Class(Base):
client_id: Mapped[int] = mapped_column(Integer)
# Unique constraint compuesta
class_code: Mapped[str] = mapped_column(String(8)) #CLASE
class_code: Mapped[str] = mapped_column(String(8)) # CLASE
# Basic information
description_es: Mapped[Optional[str]] = mapped_column(String(500)) #DESCRIPCIONE
description_en: Mapped[Optional[str]] = mapped_column(String(500)) #DESCRIPCIONI
description_es: Mapped[Optional[str]] = mapped_column(String(500)) # DESCRIPCIONE
description_en: Mapped[Optional[str]] = mapped_column(String(500)) # DESCRIPCIONI
# Material and measurement
material_key: Mapped[Optional[str]] = mapped_column(String(10), ForeignKey('public.material_types.key')) # CLAVEMAT - homologated from TIPOMAT/TIPOMATEQUIPO
unit_of_measure: Mapped[Optional[str]] = mapped_column(String(5)) # UNIMED - homologated from UNIMEDIDA
material_key: Mapped[Optional[str]] = mapped_column(
String(10), ForeignKey("public.material_types.key")
) # CLAVEMAT - homologated from TIPOMAT/TIPOMATEQUIPO
unit_of_measure: Mapped[Optional[str]] = mapped_column(
String(5)
) # UNIMED - homologated from UNIMEDIDA
# Tariff fractions
fraction: Mapped[Optional[str]] = mapped_column(String(10)) # FRACCION
us_fraction: Mapped[Optional[str]] = mapped_column(String(16)) # FRACCIONAME - US tariff fraction
us_fraction: Mapped[Optional[str]] = mapped_column(
String(16)
) # FRACCIONAME - US tariff fraction
# Additional classification
sub_key: Mapped[Optional[str]] = mapped_column(String(5)) # CLAVESUB
physical_review: Mapped[Optional[int]] = mapped_column(SmallInteger) # REVFISICA
iva_exempt_fraction: Mapped[Optional[str]] = mapped_column(String(4)) # FRACCIONEXENTAIVA
iva_exempt_fraction: Mapped[Optional[str]] = mapped_column(
String(4)
) # FRACCIONEXENTAIVA
# Relationships
material_type: Mapped[Optional["MaterialType"]] = relationship(foreign_keys=[material_key])
material_type: Mapped[Optional["MaterialType"]] = relationship(
foreign_keys=[material_key]
)
# Inverse relationship with GParts that have this class
parts: Mapped[list["Part"]] = relationship(
primaryjoin="and_(Class.client_id == Part.client_id, Class.class_code == Part.part_class)",
foreign_keys="[Part.client_id, Part.part_class]",
viewonly=True,
back_populates="part_class_info"
back_populates="part_class_info",
)
def __repr__(self) -> str:
return f"<Class(client_id={self.client_id}, class_code='{self.class_code}', description='{self.description_es}')>"

View File

@@ -1,6 +1,7 @@
"""
Endpoints API para gestión de clases SCAII y SCAF
"""
from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy.orm import Session
from typing import List, Optional
@@ -14,23 +15,28 @@ from .dto import (
ClassResponseDTO,
ClassBasicDTO,
ClassListDTO,
ClassSearchDTO
ClassSearchDTO,
)
router = APIRouter(prefix="/classes", tags=["Classes"])
@router.get("/", response_model=ClassListDTO)
async def list_classes(
skip: int = Query(0, ge=0, description="Number of records to skip"),
limit: int = Query(100, ge=1, le=1000, description="Maximum number of records to return"),
limit: int = Query(
100, ge=1, le=1000, description="Maximum number of records to return"
),
client_id: Optional[int] = Query(None, description="Filter by client key"),
class_code: Optional[str] = Query(None, description="Search by class code"),
description: Optional[str] = Query(None, description="Search in descriptions"),
material_key: Optional[str] = Query(None, description="Filter by material key"),
fraction: Optional[str] = Query(None, description="Filter by tariff fraction"),
physical_review: Optional[int] = Query(None, description="Filter by physical review indicator"),
physical_review: Optional[int] = Query(
None, description="Filter by physical review indicator"
),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
List classes with optional filters and pagination
@@ -49,7 +55,7 @@ async def list_classes(
description=description,
material_key=material_key,
fraction=fraction,
physical_review=physical_review
physical_review=physical_review,
)
return service.list_classes(skip, limit, search_params)
@@ -60,7 +66,7 @@ async def get_classes_by_client(
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Get all classes for a specific client
@@ -80,7 +86,7 @@ async def get_classes_by_client(
async def search_by_fraction(
fraction: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Search classes by tariff fraction
@@ -93,7 +99,7 @@ async def search_by_fraction(
async def search_by_material(
material_key: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Search classes by material key
@@ -102,11 +108,13 @@ async def search_by_material(
return service.search_by_material(material_key)
@router.get("/search/unit-measure/{unit_of_measure}", response_model=List[ClassBasicDTO])
@router.get(
"/search/unit-measure/{unit_of_measure}", response_model=List[ClassBasicDTO]
)
async def get_classes_by_unit_measure(
unit_of_measure: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Get classes by unit of measure
@@ -115,11 +123,13 @@ async def get_classes_by_unit_measure(
return service.get_classes_by_unit_measure(unit_of_measure)
@router.get("/search/physical-review/{physical_review}", response_model=List[ClassBasicDTO])
@router.get(
"/search/physical-review/{physical_review}", response_model=List[ClassBasicDTO]
)
async def get_classes_by_physical_review(
physical_review: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Get classes by physical review indicator
@@ -130,8 +140,7 @@ async def get_classes_by_physical_review(
@router.get("/statistics", response_model=dict)
async def get_classes_statistics(
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)
):
"""
Get basic classes statistics
@@ -145,7 +154,7 @@ async def get_class(
client_id: int,
class_code: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Get class by composite key (client_id + class_code)
@@ -155,15 +164,16 @@ async def get_class(
if not class_obj:
raise HTTPException(
status_code=404,
detail=f"Class with client_id '{client_id}' and class_code '{class_code}' not found"
detail=f"Class with client_id '{client_id}' and class_code '{class_code}' not found",
)
return class_obj
@router.post("/", response_model=ClassResponseDTO, status_code=status.HTTP_201_CREATED)
async def create_class(
class_data: ClassCreateDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Create a new class in the system
@@ -171,13 +181,14 @@ async def create_class(
service = ClassService(db)
return service.create_class(class_data)
@router.put("/{client_id}/{class_code}", response_model=ClassResponseDTO)
async def update_class(
client_id: int,
class_code: str,
class_data: ClassUpdateDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Update class information
@@ -187,7 +198,7 @@ async def update_class(
if not class_obj:
raise HTTPException(
status_code=404,
detail=f"Class with client_id '{client_id}' and class_code '{class_code}' not found"
detail=f"Class with client_id '{client_id}' and class_code '{class_code}' not found",
)
return class_obj
@@ -197,7 +208,7 @@ async def delete_class(
client_id: int,
class_code: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Delete class from the system
@@ -208,7 +219,7 @@ async def delete_class(
if not service.delete_class(client_id, class_code):
raise HTTPException(
status_code=404,
detail=f"Class with client_id '{client_id}' and class_code '{class_code}' not found"
detail=f"Class with client_id '{client_id}' and class_code '{class_code}' not found",
)
@@ -218,7 +229,7 @@ async def get_class_basic_info(
client_id: int,
class_code: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Get basic information for a class
@@ -228,7 +239,7 @@ async def get_class_basic_info(
if not class_obj:
raise HTTPException(
status_code=404,
detail=f"Class with client_id '{client_id}' and class_code '{class_code}' not found"
detail=f"Class with client_id '{client_id}' and class_code '{class_code}' not found",
)
return ClassBasicDTO(
@@ -237,7 +248,7 @@ async def get_class_basic_info(
description_spanish=class_obj.description_spanish,
description_english=class_obj.description_english,
material_key=class_obj.material_key,
fraction=class_obj.fraction
fraction=class_obj.fraction,
)
@@ -246,7 +257,7 @@ async def get_class_tariff_info(
client_id: int,
class_code: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Get tariff information for a class (fractions, IVA exempt, etc.)
@@ -256,7 +267,7 @@ async def get_class_tariff_info(
if not class_obj:
raise HTTPException(
status_code=404,
detail=f"Class with client_id '{client_id}' and class_code '{class_code}' not found"
detail=f"Class with client_id '{client_id}' and class_code '{class_code}' not found",
)
return {
@@ -266,7 +277,5 @@ async def get_class_tariff_info(
"us_fraction": class_obj.us_fraction,
"iva_exempt_fraction": class_obj.iva_exempt_fraction,
"sub_key": class_obj.sub_key,
"physical_review": class_obj.physical_review
"physical_review": class_obj.physical_review,
}

View File

@@ -1,6 +1,7 @@
"""
Capa de servicio para lógica de negocio de clases SCAII y SCAF
"""
from sqlalchemy.orm import Session
from sqlalchemy.exc import IntegrityError
from sqlalchemy import or_, and_, func
@@ -15,7 +16,7 @@ from .dto import (
ClassResponseDTO,
ClassBasicDTO,
ClassListDTO,
ClassSearchDTO
ClassSearchDTO,
)
logger = logging.getLogger(__name__)
@@ -42,17 +43,21 @@ class ClassService:
"""
try:
# Verificar que no exista la clase
existing = self.db.query(Class).filter(
and_(
Class.client_id == class_data.client_id,
Class.class_code == class_data.class_code
existing = (
self.db.query(Class)
.filter(
and_(
Class.client_id == class_data.client_id,
Class.class_code == class_data.class_code,
)
)
).first()
.first()
)
if existing:
raise HTTPException(
status_code=400,
detail=f"Class with client_id '{class_data.client_id}' and class_code '{class_data.class_code}' already exists"
detail=f"Class with client_id '{class_data.client_id}' and class_code '{class_data.class_code}' already exists",
)
# Crear clase
@@ -67,7 +72,7 @@ class ClassService:
us_fraction=class_data.us_fraction,
sub_key=class_data.sub_key,
physical_review=class_data.physical_review,
iva_exempt_fraction=class_data.iva_exempt_fraction
iva_exempt_fraction=class_data.iva_exempt_fraction,
)
self.db.add(db_class)
@@ -81,7 +86,10 @@ class ClassService:
except IntegrityError as e:
self.db.rollback()
logger.error(f"IntegrityError creating class: {str(e)}")
raise HTTPException(status_code=400, detail="Class with this client_id and class_code already exists")
raise HTTPException(
status_code=400,
detail="Class with this client_id and class_code already exists",
)
except HTTPException:
raise
except Exception as e:
@@ -100,12 +108,11 @@ class ClassService:
Returns:
ClassResponseDTO o None si no existe
"""
class_obj = self.db.query(Class).filter(
and_(
Class.client_id == client_id,
Class.class_code == class_code
)
).first()
class_obj = (
self.db.query(Class)
.filter(and_(Class.client_id == client_id, Class.class_code == class_code))
.first()
)
if not class_obj:
return None
@@ -115,7 +122,7 @@ class ClassService:
self,
skip: int = 0,
limit: int = 100,
search_params: Optional[ClassSearchDTO] = None
search_params: Optional[ClassSearchDTO] = None,
) -> ClassListDTO:
"""
Lista clases con filtros
@@ -136,25 +143,33 @@ class ClassService:
query = query.filter(Class.client_id == search_params.client_id)
if search_params.class_code:
query = query.filter(Class.class_code.ilike(f"%{search_params.class_code}%"))
query = query.filter(
Class.class_code.ilike(f"%{search_params.class_code}%")
)
if search_params.description:
description_pattern = f"%{search_params.description}%"
query = query.filter(
or_(
Class.description_spanish.ilike(description_pattern),
Class.description_english.ilike(description_pattern)
Class.description_english.ilike(description_pattern),
)
)
if search_params.material_key:
query = query.filter(Class.material_key.ilike(f"%{search_params.material_key}%"))
query = query.filter(
Class.material_key.ilike(f"%{search_params.material_key}%")
)
if search_params.fraction:
query = query.filter(Class.fraction.ilike(f"%{search_params.fraction}%"))
query = query.filter(
Class.fraction.ilike(f"%{search_params.fraction}%")
)
if search_params.physical_review is not None:
query = query.filter(Class.physical_review == search_params.physical_review)
query = query.filter(
Class.physical_review == search_params.physical_review
)
# Contar total
total = query.count()
@@ -169,10 +184,12 @@ class ClassService:
classes=class_dtos,
total=total,
page=(skip // limit) + 1 if limit > 0 else 1,
size=len(class_dtos)
size=len(class_dtos),
)
def update_class(self, client_id: int, class_code: str, class_data: ClassUpdateDTO) -> Optional[ClassResponseDTO]:
def update_class(
self, client_id: int, class_code: str, class_data: ClassUpdateDTO
) -> Optional[ClassResponseDTO]:
"""
Actualiza una clase
@@ -184,12 +201,11 @@ class ClassService:
Returns:
ClassResponseDTO actualizado o None si no existe
"""
class_obj = self.db.query(Class).filter(
and_(
Class.client_id == client_id,
Class.class_code == class_code
)
).first()
class_obj = (
self.db.query(Class)
.filter(and_(Class.client_id == client_id, Class.class_code == class_code))
.first()
)
if not class_obj:
return None
@@ -222,12 +238,11 @@ class ClassService:
Returns:
True si se eliminó, False si no existe
"""
class_obj = self.db.query(Class).filter(
and_(
Class.client_id == client_id,
Class.class_code == class_code
)
).first()
class_obj = (
self.db.query(Class)
.filter(and_(Class.client_id == client_id, Class.class_code == class_code))
.first()
)
if not class_obj:
return False
@@ -244,22 +259,40 @@ class ClassService:
def search_by_fraction(self, fraction: str) -> List[ClassBasicDTO]:
"""Busca clases por fracción arancelaria"""
classes = self.db.query(Class).filter(Class.fraction.ilike(f"%{fraction}%")).all()
classes = (
self.db.query(Class).filter(Class.fraction.ilike(f"%{fraction}%")).all()
)
return [ClassBasicDTO.model_validate(class_obj) for class_obj in classes]
def search_by_client(self, client_id: int, skip: int = 0, limit: int = 100) -> List[ClassBasicDTO]:
def search_by_client(
self, client_id: int, skip: int = 0, limit: int = 100
) -> List[ClassBasicDTO]:
"""Obtiene todas las clases de un cliente específico"""
classes = self.db.query(Class).filter(Class.client_id == client_id).offset(skip).limit(limit).all()
classes = (
self.db.query(Class)
.filter(Class.client_id == client_id)
.offset(skip)
.limit(limit)
.all()
)
return [ClassBasicDTO.model_validate(class_obj) for class_obj in classes]
def search_by_material(self, material_key: str) -> List[ClassBasicDTO]:
"""Busca clases por clave de material"""
classes = self.db.query(Class).filter(Class.material_key.ilike(f"%{material_key}%")).all()
classes = (
self.db.query(Class)
.filter(Class.material_key.ilike(f"%{material_key}%"))
.all()
)
return [ClassBasicDTO.model_validate(class_obj) for class_obj in classes]
def get_classes_by_physical_review(self, physical_review: int) -> List[ClassBasicDTO]:
def get_classes_by_physical_review(
self, physical_review: int
) -> List[ClassBasicDTO]:
"""Obtiene clases por indicador de revisión física"""
classes = self.db.query(Class).filter(Class.physical_review == physical_review).all()
classes = (
self.db.query(Class).filter(Class.physical_review == physical_review).all()
)
return [ClassBasicDTO.model_validate(class_obj) for class_obj in classes]
def get_classes_statistics(self) -> dict:
@@ -277,18 +310,21 @@ class ClassService:
# Contar clases con fracciones
with_fraction = self.db.query(Class).filter(Class.fraction.isnot(None)).count()
with_us_fraction = self.db.query(Class).filter(Class.us_fraction.isnot(None)).count()
with_us_fraction = (
self.db.query(Class).filter(Class.us_fraction.isnot(None)).count()
)
return {
"total_classes": total_classes,
"clients_with_classes": clients_count,
"classes_with_fraction": with_fraction,
"classes_with_us_fraction": with_us_fraction,
**physical_review_stats
**physical_review_stats,
}
def get_classes_by_unit_measure(self, unit_of_measure: str) -> List[ClassBasicDTO]:
"""Obtiene clases por unidad de medida"""
classes = self.db.query(Class).filter(Class.unit_of_measure == unit_of_measure).all()
classes = (
self.db.query(Class).filter(Class.unit_of_measure == unit_of_measure).all()
)
return [ClassBasicDTO.model_validate(class_obj) for class_obj in classes]

View File

@@ -1,6 +1,7 @@
"""
Módulo de Client & Provider
"""
from .routes import router
__all__ = ["router"]

View File

@@ -2,6 +2,7 @@
DTOs (Data Transfer Objects) para módulo de clientes y proveedores
Reemplaza schemas.py siguiendo enfoque DDD y estilo NestJS
"""
from pydantic import BaseModel, Field, EmailStr
from typing import Optional
from datetime import datetime
@@ -11,11 +12,18 @@ from decimal import Decimal
# DTOs para dirección
class ClientProviderAddressDTO(BaseModel):
"""DTO para dirección de cliente/proveedor"""
municipality: Optional[str] = Field(None, max_length=150, description="Municipality")
municipality: Optional[str] = Field(
None, max_length=150, description="Municipality"
)
streets: Optional[str] = Field(None, max_length=100, description="Streets")
neighborhood: Optional[str] = Field(None, max_length=40, description="Neighborhood")
interior_number: Optional[str] = Field(None, max_length=20, description="Interior number")
exterior_number: Optional[str] = Field(None, max_length=20, description="Exterior number")
interior_number: Optional[str] = Field(
None, max_length=20, description="Interior number"
)
exterior_number: Optional[str] = Field(
None, max_length=20, description="Exterior number"
)
postal_code: Optional[str] = Field(None, max_length=15, description="Postal code")
city: Optional[str] = Field(None, max_length=30, description="City")
state: Optional[str] = Field(None, max_length=30, description="State")
@@ -33,26 +41,49 @@ class ClientProviderAddressDTO(BaseModel):
# DTOs para programas
class ClientProviderProgramsDTO(BaseModel):
"""DTO para programas de cliente/proveedor"""
program: Optional[str] = Field(None, max_length=7, description="Program")
program_number: Optional[str] = Field(None, max_length=40, description="Program number")
program_number: Optional[str] = Field(
None, max_length=40, description="Program number"
)
prosec: Optional[int] = Field(None, description="PROSEC")
prosec_authorization: Optional[str] = Field(None, max_length=20, description="PROSEC authorization")
prosec_authorization: Optional[str] = Field(
None, max_length=20, description="PROSEC authorization"
)
secon_auth_date: Optional[int] = Field(None, description="SECON authorization date")
manufacturer_id: Optional[str] = Field(None, max_length=25, description="Manufacturer ID")
manufacturer_id: Optional[str] = Field(
None, max_length=25, description="Manufacturer ID"
)
tax_id: Optional[str] = Field(None, max_length=30, description="Tax ID")
broker: Optional[str] = Field(None, max_length=6, description="Broker")
import_broker: Optional[str] = Field(None, max_length=6, description="Import broker")
import_broker: Optional[str] = Field(
None, max_length=6, description="Import broker"
)
transfer_key: Optional[str] = Field(None, max_length=8, description="Transfer key")
secon_authorization: Optional[str] = Field(None, max_length=20, description="SECON authorization")
applied_proportion: Optional[Decimal] = Field(None, description="Applied proportion")
is_certified_company: Optional[str] = Field(None, max_length=1, description="Is certified company")
certified_company_registry: Optional[str] = Field(None, max_length=40, description="Certified company registry")
donation_auth_number: Optional[str] = Field(None, max_length=50, description="Donation authorization number")
secon_authorization: Optional[str] = Field(
None, max_length=20, description="SECON authorization"
)
applied_proportion: Optional[Decimal] = Field(
None, description="Applied proportion"
)
is_certified_company: Optional[str] = Field(
None, max_length=1, description="Is certified company"
)
certified_company_registry: Optional[str] = Field(
None, max_length=40, description="Certified company registry"
)
donation_auth_number: Optional[str] = Field(
None, max_length=50, description="Donation authorization number"
)
ctpat_svi: Optional[str] = Field(None, max_length=100, description="CTPAT SVI")
tax_registry_number: Optional[str] = Field(None, max_length=40, description="Tax registry number")
tax_registry_number: Optional[str] = Field(
None, max_length=40, description="Tax registry number"
)
subassembly_service: Optional[int] = Field(None, description="Subassembly service")
autse_dates: Optional[int] = Field(None, description="AUTSE dates")
autse_number: Optional[str] = Field(None, max_length=300, description="AUTSE number")
autse_number: Optional[str] = Field(
None, max_length=300, description="AUTSE number"
)
class Config:
from_attributes = True
@@ -61,26 +92,43 @@ class ClientProviderProgramsDTO(BaseModel):
# DTOs principales
class ClientProviderCreateDTO(BaseModel):
"""DTO para crear cliente/proveedor"""
client_id: str = Field(..., max_length=8, description="Client ID")
type_nat_foreign: Optional[str] = Field(None, max_length=1, description="Type national/foreign")
type_nat_foreign: Optional[str] = Field(
None, max_length=1, description="Type national/foreign"
)
name: Optional[str] = Field(None, max_length=256, description="Name")
short_name: Optional[str] = Field(None, max_length=10, description="Short name")
rfc: Optional[str] = Field(None, max_length=30, description="RFC")
curp: Optional[str] = Field(None, max_length=19, description="CURP")
client_or_provider: Optional[str] = Field(None, max_length=1, description="Client or provider")
client_or_provider: Optional[str] = Field(
None, max_length=1, description="Client or provider"
)
linking: Optional[str] = Field(None, max_length=1, description="Linking")
transform_subassembly: Optional[str] = Field(None, max_length=1, description="Transform subassembly")
extra_information: Optional[str] = Field(None, max_length=399, description="Extra information")
transform_subassembly: Optional[str] = Field(
None, max_length=1, description="Transform subassembly"
)
extra_information: Optional[str] = Field(
None, max_length=399, description="Extra information"
)
web_key: Optional[str] = Field(None, max_length=40, description="Web key")
responsible: Optional[str] = Field(None, max_length=80, description="Responsible person")
responsible: Optional[str] = Field(
None, max_length=80, description="Responsible person"
)
position: Optional[str] = Field(None, max_length=30, description="Position")
incoterm: Optional[str] = Field(None, max_length=19, description="Incoterm")
is_national_provider: Optional[str] = Field(None, max_length=2, description="Is national provider")
is_national_provider: Optional[str] = Field(
None, max_length=2, description="Is national provider"
)
enabled_disabled: Optional[int] = Field(None, description="Enabled/Disabled status")
# Nested DTOs
address: Optional[ClientProviderAddressDTO] = Field(None, description="Address information")
programs: Optional[ClientProviderProgramsDTO] = Field(None, description="Programs information")
address: Optional[ClientProviderAddressDTO] = Field(
None, description="Address information"
)
programs: Optional[ClientProviderProgramsDTO] = Field(
None, description="Programs information"
)
class Config:
from_attributes = True
@@ -88,25 +136,42 @@ class ClientProviderCreateDTO(BaseModel):
class ClientProviderUpdateDTO(BaseModel):
"""DTO para actualizar cliente/proveedor"""
type_nat_foreign: Optional[str] = Field(None, max_length=1, description="Type national/foreign")
type_nat_foreign: Optional[str] = Field(
None, max_length=1, description="Type national/foreign"
)
name: Optional[str] = Field(None, max_length=256, description="Name")
short_name: Optional[str] = Field(None, max_length=10, description="Short name")
rfc: Optional[str] = Field(None, max_length=30, description="RFC")
curp: Optional[str] = Field(None, max_length=19, description="CURP")
client_or_provider: Optional[str] = Field(None, max_length=1, description="Client or provider")
client_or_provider: Optional[str] = Field(
None, max_length=1, description="Client or provider"
)
linking: Optional[str] = Field(None, max_length=1, description="Linking")
transform_subassembly: Optional[str] = Field(None, max_length=1, description="Transform subassembly")
extra_information: Optional[str] = Field(None, max_length=399, description="Extra information")
transform_subassembly: Optional[str] = Field(
None, max_length=1, description="Transform subassembly"
)
extra_information: Optional[str] = Field(
None, max_length=399, description="Extra information"
)
web_key: Optional[str] = Field(None, max_length=40, description="Web key")
responsible: Optional[str] = Field(None, max_length=80, description="Responsible person")
responsible: Optional[str] = Field(
None, max_length=80, description="Responsible person"
)
position: Optional[str] = Field(None, max_length=30, description="Position")
incoterm: Optional[str] = Field(None, max_length=19, description="Incoterm")
is_national_provider: Optional[str] = Field(None, max_length=2, description="Is national provider")
is_national_provider: Optional[str] = Field(
None, max_length=2, description="Is national provider"
)
enabled_disabled: Optional[int] = Field(None, description="Enabled/Disabled status")
# Nested DTOs
address: Optional[ClientProviderAddressDTO] = Field(None, description="Address information")
programs: Optional[ClientProviderProgramsDTO] = Field(None, description="Programs information")
address: Optional[ClientProviderAddressDTO] = Field(
None, description="Address information"
)
programs: Optional[ClientProviderProgramsDTO] = Field(
None, description="Programs information"
)
class Config:
from_attributes = True
@@ -114,6 +179,7 @@ class ClientProviderUpdateDTO(BaseModel):
class ClientProviderResponseDTO(BaseModel):
"""DTO para respuesta de cliente/proveedor"""
client_id: str
type_nat_foreign: Optional[str] = None
name: Optional[str] = None
@@ -142,6 +208,7 @@ class ClientProviderResponseDTO(BaseModel):
# DTOs para respuestas específicas
class ClientProviderBasicDTO(BaseModel):
"""DTO para información básica de cliente/proveedor"""
client_id: str
name: Optional[str] = None
short_name: Optional[str] = None
@@ -155,6 +222,7 @@ class ClientProviderBasicDTO(BaseModel):
class ClientProviderListDTO(BaseModel):
"""DTO para lista de clientes/proveedores"""
clients: list[ClientProviderBasicDTO]
total: int
page: int
@@ -162,4 +230,3 @@ class ClientProviderListDTO(BaseModel):
class Config:
from_attributes = True

View File

@@ -1,9 +1,18 @@
"""
Modelos ORM para gestión de clientes y proveedores
"""
from typing import Optional
from decimal import Decimal
from sqlalchemy import Integer, String, SmallInteger, Numeric, ForeignKey, PrimaryKeyConstraint, ForeignKeyConstraint
from sqlalchemy import (
Integer,
String,
SmallInteger,
Numeric,
ForeignKey,
PrimaryKeyConstraint,
ForeignKeyConstraint,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
from core.database import Base
@@ -12,12 +21,17 @@ class ClientProvider(Base):
"""
Modelo para la tabla GClientesPro - Información de clientes y proveedores
"""
__tablename__ = "client_provider"
__table_args__ = (
PrimaryKeyConstraint('id', name='client_provider_pkey'),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_client_provider_tenant'),
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_client_provider_company'),
{"schema": "a76"}
PrimaryKeyConstraint("id", name="client_provider_pkey"),
ForeignKeyConstraint(
["tenant_id"], ["a76.tenants.id"], name="fk_client_provider_tenant"
),
ForeignKeyConstraint(
["company_id"], ["a76.company.id"], name="fk_client_provider_company"
),
{"schema": "a76"},
)
# Primary key
@@ -26,7 +40,9 @@ class ClientProvider(Base):
company_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
# Basic information
type_nat_foreign: Mapped[Optional[str]] = mapped_column(String(1)) # TIPO NACIONAL/EXTRANJERO
type_nat_foreign: Mapped[Optional[str]] = mapped_column(
String(1)
) # TIPO NACIONAL/EXTRANJERO
name: Mapped[Optional[str]] = mapped_column(String(256))
short_name: Mapped[Optional[str]] = mapped_column(String(10))
rfc: Mapped[Optional[str]] = mapped_column(String(30))
@@ -43,25 +59,39 @@ class ClientProvider(Base):
enabled_disabled: Mapped[Optional[int]] = mapped_column(SmallInteger)
# Relationships
address: Mapped[Optional["ClientProviderAddress"]] = relationship(back_populates="client_provider", uselist=False, cascade="all, delete-orphan")
programs: Mapped[Optional["ClientProviderPrograms"]] = relationship(back_populates="client_provider", uselist=False, cascade="all, delete-orphan")
address: Mapped[Optional["ClientProviderAddress"]] = relationship(
back_populates="client_provider", uselist=False, cascade="all, delete-orphan"
)
programs: Mapped[Optional["ClientProviderPrograms"]] = relationship(
back_populates="client_provider", uselist=False, cascade="all, delete-orphan"
)
class ClientProviderAddress(Base):
"""
Modelo para la tabla GClientesPro_Direccion - Dirección de clientes y proveedores
"""
__tablename__ = "client_provider_address"
__table_args__ = (
PrimaryKeyConstraint('id', name='client_provider_address_pkey'),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_client_provider_address_tenant'),
ForeignKeyConstraint(['client_id'], ['a76.client_provider.id'], ondelete='CASCADE', name='fk_client_provider_address_client'),
{"schema": "a76"}
PrimaryKeyConstraint("id", name="client_provider_address_pkey"),
ForeignKeyConstraint(
["tenant_id"], ["a76.tenants.id"], name="fk_client_provider_address_tenant"
),
ForeignKeyConstraint(
["client_id"],
["a76.client_provider.id"],
ondelete="CASCADE",
name="fk_client_provider_address_client",
),
{"schema": "a76"},
)
# Primary key (foreign key)
id: Mapped[int] = mapped_column(Integer, primary_key=True)
client_id: Mapped[int] = mapped_column(Integer, ForeignKey('a76.client_provider.id', ondelete='CASCADE'))
client_id: Mapped[int] = mapped_column(
Integer, ForeignKey("a76.client_provider.id", ondelete="CASCADE")
)
tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
@@ -89,17 +119,27 @@ class ClientProviderPrograms(Base):
"""
Modelo para la tabla GClientesPro_Programas - Programas de clientes y proveedores
"""
__tablename__ = "client_provider_programs"
__table_args__ = (
PrimaryKeyConstraint('id', name='client_provider_programs_pkey'),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_client_provider_programs_tenant'),
ForeignKeyConstraint(['client_id'], ['a76.client_provider.id'], ondelete='CASCADE', name='fk_client_provider_programs_client'),
{"schema": "a76"}
PrimaryKeyConstraint("id", name="client_provider_programs_pkey"),
ForeignKeyConstraint(
["tenant_id"], ["a76.tenants.id"], name="fk_client_provider_programs_tenant"
),
ForeignKeyConstraint(
["client_id"],
["a76.client_provider.id"],
ondelete="CASCADE",
name="fk_client_provider_programs_client",
),
{"schema": "a76"},
)
# Primary key (foreign key)
id: Mapped[int] = mapped_column(Integer, primary_key=True)
client_id: Mapped[int] = mapped_column(Integer, ForeignKey('a76.client_provider.id', ondelete='CASCADE'))
client_id: Mapped[int] = mapped_column(
Integer, ForeignKey("a76.client_provider.id", ondelete="CASCADE")
)
tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
@@ -127,5 +167,3 @@ class ClientProviderPrograms(Base):
# Relationship
client_provider: Mapped["ClientProvider"] = relationship(back_populates="programs")

View File

@@ -1,6 +1,7 @@
"""
Endpoints API para gestión de clientes y proveedores
"""
from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy.orm import Session
from typing import List, Optional
@@ -13,17 +14,19 @@ from .dto import (
ClientProviderUpdateDTO,
ClientProviderResponseDTO,
ClientProviderBasicDTO,
ClientProviderListDTO
ClientProviderListDTO,
)
router = APIRouter(prefix="/clients-providers")
@router.post("/", response_model=ClientProviderResponseDTO, status_code=status.HTTP_201_CREATED)
@router.post(
"/", response_model=ClientProviderResponseDTO, status_code=status.HTTP_201_CREATED
)
async def create_client_provider(
client_data: ClientProviderCreateDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Create a new client or provider in the system
@@ -46,12 +49,16 @@ async def create_client_provider(
@router.get("/", response_model=ClientProviderListDTO)
async def list_clients_providers(
skip: int = Query(0, ge=0, description="Number of records to skip"),
limit: int = Query(100, ge=1, le=1000, description="Maximum number of records to return"),
limit: int = Query(
100, ge=1, le=1000, description="Maximum number of records to return"
),
search: Optional[str] = Query(None, description="Search text for name, RFC, or ID"),
client_or_provider: Optional[str] = Query(None, regex="^[CP]$", description="Filter by type: C=Client, P=Provider"),
client_or_provider: Optional[str] = Query(
None, regex="^[CP]$", description="Filter by type: C=Client, P=Provider"
),
enabled_only: bool = Query(False, description="Show only enabled records"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
List clients and providers with optional filters and pagination
@@ -64,7 +71,9 @@ async def list_clients_providers(
raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found")
service = ClientProviderService(db)
return service.list_clients_providers(skip, limit, search, client_or_provider, enabled_only)
return service.list_clients_providers(
skip, limit, search, client_or_provider, enabled_only
)
@router.get("/clients", response_model=List[ClientProviderBasicDTO])
@@ -72,7 +81,7 @@ async def get_clients_only(
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Get only clients (client_or_provider = 'C')
@@ -93,7 +102,7 @@ async def get_providers_only(
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Get only providers (client_or_provider = 'P')
@@ -113,7 +122,7 @@ async def get_providers_only(
async def search_by_rfc(
rfc: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Search clients/providers by RFC
@@ -133,7 +142,7 @@ async def search_by_rfc(
async def get_client_provider(
client_id: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Get client/provider by ID with all related information
@@ -148,7 +157,9 @@ async def get_client_provider(
service = ClientProviderService(db)
client = service.get_client_provider(client_id)
if not client:
raise HTTPException(status_code=404, detail=f"Client/Provider with ID '{client_id}' not found")
raise HTTPException(
status_code=404, detail=f"Client/Provider with ID '{client_id}' not found"
)
return client
@@ -157,7 +168,7 @@ async def update_client_provider(
client_id: str,
client_data: ClientProviderUpdateDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Update client/provider information
@@ -172,7 +183,9 @@ async def update_client_provider(
service = ClientProviderService(db)
client = service.update_client_provider(client_id, client_data)
if not client:
raise HTTPException(status_code=404, detail=f"Client/Provider with ID '{client_id}' not found")
raise HTTPException(
status_code=404, detail=f"Client/Provider with ID '{client_id}' not found"
)
return client
@@ -180,7 +193,7 @@ async def update_client_provider(
async def delete_client_provider(
client_id: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Delete client/provider from the system
@@ -196,14 +209,16 @@ async def delete_client_provider(
service = ClientProviderService(db)
if not service.delete_client_provider(client_id):
raise HTTPException(status_code=404, detail=f"Client/Provider with ID '{client_id}' not found")
raise HTTPException(
status_code=404, detail=f"Client/Provider with ID '{client_id}' not found"
)
@router.patch("/{client_id}/toggle-status", response_model=ClientProviderResponseDTO)
async def toggle_client_provider_status(
client_id: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Toggle client/provider enabled/disabled status
@@ -218,7 +233,9 @@ async def toggle_client_provider_status(
service = ClientProviderService(db)
client = service.toggle_status(client_id)
if not client:
raise HTTPException(status_code=404, detail=f"Client/Provider with ID '{client_id}' not found")
raise HTTPException(
status_code=404, detail=f"Client/Provider with ID '{client_id}' not found"
)
return client
@@ -227,7 +244,7 @@ async def toggle_client_provider_status(
async def get_client_provider_address(
client_id: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Get only address information for a client/provider
@@ -242,19 +259,18 @@ async def get_client_provider_address(
service = ClientProviderService(db)
client = service.get_client_provider(client_id)
if not client:
raise HTTPException(status_code=404, detail=f"Client/Provider with ID '{client_id}' not found")
raise HTTPException(
status_code=404, detail=f"Client/Provider with ID '{client_id}' not found"
)
return {
"client_id": client.client_id,
"address": client.address
}
return {"client_id": client.client_id, "address": client.address}
@router.get("/{client_id}/programs", response_model=dict)
async def get_client_provider_programs(
client_id: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Get only programs information for a client/provider
@@ -269,19 +285,18 @@ async def get_client_provider_programs(
service = ClientProviderService(db)
client = service.get_client_provider(client_id)
if not client:
raise HTTPException(status_code=404, detail=f"Client/Provider with ID '{client_id}' not found")
raise HTTPException(
status_code=404, detail=f"Client/Provider with ID '{client_id}' not found"
)
return {
"client_id": client.client_id,
"programs": client.programs
}
return {"client_id": client.client_id, "programs": client.programs}
@router.get("/{client_id}/basic", response_model=ClientProviderBasicDTO)
async def get_client_provider_basic_info(
client_id: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Get basic information for a client/provider (without address and programs)
@@ -296,7 +311,9 @@ async def get_client_provider_basic_info(
service = ClientProviderService(db)
client = service.get_client_provider(client_id)
if not client:
raise HTTPException(status_code=404, detail=f"Client/Provider with ID '{client_id}' not found")
raise HTTPException(
status_code=404, detail=f"Client/Provider with ID '{client_id}' not found"
)
return ClientProviderBasicDTO(
client_id=client.client_id,
@@ -304,6 +321,5 @@ async def get_client_provider_basic_info(
short_name=client.short_name,
rfc=client.rfc,
client_or_provider=client.client_or_provider,
enabled_disabled=client.enabled_disabled
enabled_disabled=client.enabled_disabled,
)

View File

@@ -1,6 +1,7 @@
"""
Capa de servicio para lógica de negocio de clientes y proveedores
"""
from sqlalchemy.orm import Session, joinedload
from sqlalchemy.exc import IntegrityError
from sqlalchemy import or_, and_
@@ -16,7 +17,7 @@ from .dto import (
ClientProviderBasicDTO,
ClientProviderListDTO,
ClientProviderAddressDTO,
ClientProviderProgramsDTO
ClientProviderProgramsDTO,
)
logger = logging.getLogger(__name__)
@@ -28,7 +29,9 @@ class ClientProviderService:
def __init__(self, db: Session):
self.db = db
def create_client_provider(self, client_data: ClientProviderCreateDTO) -> ClientProviderResponseDTO:
def create_client_provider(
self, client_data: ClientProviderCreateDTO
) -> ClientProviderResponseDTO:
"""
Crea un nuevo cliente/proveedor en el sistema
@@ -43,9 +46,16 @@ class ClientProviderService:
"""
try:
# Verificar que no exista el cliente
existing = self.db.query(ClientProvider).filter(ClientProvider.client_id == client_data.client_id).first()
existing = (
self.db.query(ClientProvider)
.filter(ClientProvider.client_id == client_data.client_id)
.first()
)
if existing:
raise HTTPException(status_code=400, detail=f"Client with ID '{client_data.client_id}' already exists")
raise HTTPException(
status_code=400,
detail=f"Client with ID '{client_data.client_id}' already exists",
)
# Crear cliente/proveedor principal
db_client = ClientProvider(
@@ -64,7 +74,7 @@ class ClientProviderService:
position=client_data.position,
incoterm=client_data.incoterm,
is_national_provider=client_data.is_national_provider,
enabled_disabled=client_data.enabled_disabled
enabled_disabled=client_data.enabled_disabled,
)
self.db.add(db_client)
@@ -74,7 +84,7 @@ class ClientProviderService:
if client_data.address:
db_address = ClientProviderAddress(
client_id=client_data.client_id,
**client_data.address.model_dump(exclude_unset=True)
**client_data.address.model_dump(exclude_unset=True),
)
self.db.add(db_address)
@@ -82,29 +92,37 @@ class ClientProviderService:
if client_data.programs:
db_programs = ClientProviderPrograms(
client_id=client_data.client_id,
**client_data.programs.model_dump(exclude_unset=True)
**client_data.programs.model_dump(exclude_unset=True),
)
self.db.add(db_programs)
self.db.commit()
self.db.refresh(db_client)
logger.info(f"Client/Provider created: {db_client.client_id} - {db_client.name}")
logger.info(
f"Client/Provider created: {db_client.client_id} - {db_client.name}"
)
return self._get_client_with_relations(client_data.client_id)
except IntegrityError as e:
self.db.rollback()
logger.error(f"IntegrityError creating client/provider: {str(e)}")
raise HTTPException(status_code=400, detail="Client/Provider with this ID already exists")
raise HTTPException(
status_code=400, detail="Client/Provider with this ID already exists"
)
except HTTPException:
raise
except Exception as e:
self.db.rollback()
logger.error(f"Error creating client/provider: {str(e)}")
raise HTTPException(status_code=500, detail="Error creating client/provider")
raise HTTPException(
status_code=500, detail="Error creating client/provider"
)
def get_client_provider(self, client_id: str) -> Optional[ClientProviderResponseDTO]:
def get_client_provider(
self, client_id: str
) -> Optional[ClientProviderResponseDTO]:
"""
Obtiene un cliente/proveedor por ID
@@ -116,12 +134,18 @@ class ClientProviderService:
"""
return self._get_client_with_relations(client_id)
def _get_client_with_relations(self, client_id: str) -> Optional[ClientProviderResponseDTO]:
def _get_client_with_relations(
self, client_id: str
) -> Optional[ClientProviderResponseDTO]:
"""Método privado para obtener cliente con relaciones"""
client = self.db.query(ClientProvider).options(
joinedload(ClientProvider.address),
joinedload(ClientProvider.programs)
).filter(ClientProvider.client_id == client_id).first()
client = (
self.db.query(ClientProvider)
.options(
joinedload(ClientProvider.address), joinedload(ClientProvider.programs)
)
.filter(ClientProvider.client_id == client_id)
.first()
)
if not client:
return None
@@ -133,7 +157,7 @@ class ClientProviderService:
limit: int = 100,
search: Optional[str] = None,
client_or_provider: Optional[str] = None,
enabled_only: bool = False
enabled_only: bool = False,
) -> ClientProviderListDTO:
"""
Lista clientes/proveedores con filtros
@@ -158,12 +182,14 @@ class ClientProviderService:
ClientProvider.name.ilike(search_pattern),
ClientProvider.short_name.ilike(search_pattern),
ClientProvider.rfc.ilike(search_pattern),
ClientProvider.client_id.ilike(search_pattern)
ClientProvider.client_id.ilike(search_pattern),
)
)
if client_or_provider:
query = query.filter(ClientProvider.client_or_provider == client_or_provider)
query = query.filter(
ClientProvider.client_or_provider == client_or_provider
)
if enabled_only:
query = query.filter(ClientProvider.enabled_disabled == 1)
@@ -175,16 +201,20 @@ class ClientProviderService:
clients = query.offset(skip).limit(limit).all()
# Convertir a DTOs básicos
client_dtos = [ClientProviderBasicDTO.model_validate(client) for client in clients]
client_dtos = [
ClientProviderBasicDTO.model_validate(client) for client in clients
]
return ClientProviderListDTO(
clients=client_dtos,
total=total,
page=(skip // limit) + 1 if limit > 0 else 1,
size=len(client_dtos)
size=len(client_dtos),
)
def update_client_provider(self, client_id: str, client_data: ClientProviderUpdateDTO) -> Optional[ClientProviderResponseDTO]:
def update_client_provider(
self, client_id: str, client_data: ClientProviderUpdateDTO
) -> Optional[ClientProviderResponseDTO]:
"""
Actualiza un cliente/proveedor
@@ -195,19 +225,29 @@ class ClientProviderService:
Returns:
ClientProviderResponseDTO actualizado o None si no existe
"""
client = self.db.query(ClientProvider).filter(ClientProvider.client_id == client_id).first()
client = (
self.db.query(ClientProvider)
.filter(ClientProvider.client_id == client_id)
.first()
)
if not client:
return None
try:
# Actualizar campos del cliente principal
update_data = client_data.model_dump(exclude_unset=True, exclude={'address', 'programs'})
update_data = client_data.model_dump(
exclude_unset=True, exclude={"address", "programs"}
)
for field, value in update_data.items():
setattr(client, field, value)
# Actualizar dirección
if client_data.address:
address = self.db.query(ClientProviderAddress).filter(ClientProviderAddress.client_id == client_id).first()
address = (
self.db.query(ClientProviderAddress)
.filter(ClientProviderAddress.client_id == client_id)
.first()
)
if address:
# Actualizar dirección existente
address_data = client_data.address.model_dump(exclude_unset=True)
@@ -217,13 +257,17 @@ class ClientProviderService:
# Crear nueva dirección
address = ClientProviderAddress(
client_id=client_id,
**client_data.address.model_dump(exclude_unset=True)
**client_data.address.model_dump(exclude_unset=True),
)
self.db.add(address)
# Actualizar programas
if client_data.programs:
programs = self.db.query(ClientProviderPrograms).filter(ClientProviderPrograms.client_id == client_id).first()
programs = (
self.db.query(ClientProviderPrograms)
.filter(ClientProviderPrograms.client_id == client_id)
.first()
)
if programs:
# Actualizar programas existentes
programs_data = client_data.programs.model_dump(exclude_unset=True)
@@ -233,7 +277,7 @@ class ClientProviderService:
# Crear nuevos programas
programs = ClientProviderPrograms(
client_id=client_id,
**client_data.programs.model_dump(exclude_unset=True)
**client_data.programs.model_dump(exclude_unset=True),
)
self.db.add(programs)
@@ -245,7 +289,9 @@ class ClientProviderService:
except Exception as e:
self.db.rollback()
logger.error(f"Error updating client/provider {client_id}: {str(e)}")
raise HTTPException(status_code=500, detail="Error updating client/provider")
raise HTTPException(
status_code=500, detail="Error updating client/provider"
)
def delete_client_provider(self, client_id: str) -> bool:
"""
@@ -257,7 +303,11 @@ class ClientProviderService:
Returns:
True si se eliminó, False si no existe
"""
client = self.db.query(ClientProvider).filter(ClientProvider.client_id == client_id).first()
client = (
self.db.query(ClientProvider)
.filter(ClientProvider.client_id == client_id)
.first()
)
if not client:
return False
@@ -269,28 +319,48 @@ class ClientProviderService:
except Exception as e:
self.db.rollback()
logger.error(f"Error deleting client/provider {client_id}: {str(e)}")
raise HTTPException(status_code=500, detail="Error deleting client/provider")
raise HTTPException(
status_code=500, detail="Error deleting client/provider"
)
def get_clients_only(self, skip: int = 0, limit: int = 100) -> List[ClientProviderBasicDTO]:
def get_clients_only(
self, skip: int = 0, limit: int = 100
) -> List[ClientProviderBasicDTO]:
"""Obtiene solo clientes (C)"""
query = self.db.query(ClientProvider).filter(ClientProvider.client_or_provider == 'C')
query = self.db.query(ClientProvider).filter(
ClientProvider.client_or_provider == "C"
)
clients = query.offset(skip).limit(limit).all()
return [ClientProviderBasicDTO.model_validate(client) for client in clients]
def get_providers_only(self, skip: int = 0, limit: int = 100) -> List[ClientProviderBasicDTO]:
def get_providers_only(
self, skip: int = 0, limit: int = 100
) -> List[ClientProviderBasicDTO]:
"""Obtiene solo proveedores (P)"""
query = self.db.query(ClientProvider).filter(ClientProvider.client_or_provider == 'P')
query = self.db.query(ClientProvider).filter(
ClientProvider.client_or_provider == "P"
)
providers = query.offset(skip).limit(limit).all()
return [ClientProviderBasicDTO.model_validate(provider) for provider in providers]
return [
ClientProviderBasicDTO.model_validate(provider) for provider in providers
]
def search_by_rfc(self, rfc: str) -> List[ClientProviderBasicDTO]:
"""Busca clientes/proveedores por RFC"""
clients = self.db.query(ClientProvider).filter(ClientProvider.rfc.ilike(f"%{rfc}%")).all()
clients = (
self.db.query(ClientProvider)
.filter(ClientProvider.rfc.ilike(f"%{rfc}%"))
.all()
)
return [ClientProviderBasicDTO.model_validate(client) for client in clients]
def toggle_status(self, client_id: str) -> Optional[ClientProviderResponseDTO]:
"""Cambia el estado habilitado/deshabilitado"""
client = self.db.query(ClientProvider).filter(ClientProvider.client_id == client_id).first()
client = (
self.db.query(ClientProvider)
.filter(ClientProvider.client_id == client_id)
.first()
)
if not client:
return None
@@ -299,11 +369,11 @@ class ClientProviderService:
try:
self.db.commit()
logger.info(f"Client/Provider status toggled: {client_id} -> {client.enabled_disabled}")
logger.info(
f"Client/Provider status toggled: {client_id} -> {client.enabled_disabled}"
)
return self._get_client_with_relations(client_id)
except Exception as e:
self.db.rollback()
logger.error(f"Error toggling status for {client_id}: {str(e)}")
raise HTTPException(status_code=500, detail="Error updating status")

View File

@@ -1,6 +1,7 @@
"""
Módulo de Company
"""
from .routes import router
__all__ = ["router"]

View File

@@ -2,6 +2,7 @@
DTOs (Data Transfer Objects) para módulo de empresa
Reemplaza schemas.py siguiendo enfoque DDD y estilo NestJS
"""
from pydantic import BaseModel, Field
from typing import Optional
from datetime import datetime
@@ -9,47 +10,80 @@ from datetime import datetime
class CompanyCreateDTO(BaseModel):
"""DTO para crear una empresa"""
id: str = Field(default='EMP', max_length=3, description="Company ID")
id: str = Field(default="EMP", max_length=3, description="Company ID")
consecutive: bool = Field(default=True, description="Unique record control")
name: Optional[str] = Field(None, max_length=255, description="Company name")
rfc: Optional[str] = Field(None, max_length=30, description="Company RFC")
main_activity: Optional[str] = Field(None, max_length=255, description="Main activity")
main_activity: Optional[str] = Field(
None, max_length=255, description="Main activity"
)
# Program information
program: Optional[str] = Field(None, max_length=10, description="Program")
program_number: Optional[str] = Field(None, max_length=40, description="Program number")
program_number: Optional[str] = Field(
None, max_length=40, description="Program number"
)
prosec: Optional[int] = Field(None, description="PROSEC")
prosec_authorization: Optional[str] = Field(None, max_length=20, description="PROSEC authorization")
prosec_authorization: Optional[str] = Field(
None, max_length=20, description="PROSEC authorization"
)
# Identifiers
manufacturer_id: Optional[str] = Field(None, max_length=25, description="Manufacturer ID")
broker_company: Optional[str] = Field(None, max_length=10, description="Broker company")
manufacturer_id: Optional[str] = Field(
None, max_length=25, description="Manufacturer ID"
)
broker_company: Optional[str] = Field(
None, max_length=10, description="Broker company"
)
# Responsible person
responsible: Optional[str] = Field(None, max_length=80, description="Responsible person")
responsible_name: Optional[str] = Field(None, max_length=20, description="Responsible first name")
responsible_last_name: Optional[str] = Field(None, max_length=20, description="Responsible last name")
responsible_mother_last_name: Optional[str] = Field(None, max_length=20, description="Responsible mother's last name")
responsible_rfc: Optional[str] = Field(None, max_length=30, description="Responsible RFC")
position: Optional[str] = Field(None, max_length=30, description="Responsible position")
responsible: Optional[str] = Field(
None, max_length=80, description="Responsible person"
)
responsible_name: Optional[str] = Field(
None, max_length=20, description="Responsible first name"
)
responsible_last_name: Optional[str] = Field(
None, max_length=20, description="Responsible last name"
)
responsible_mother_last_name: Optional[str] = Field(
None, max_length=20, description="Responsible mother's last name"
)
responsible_rfc: Optional[str] = Field(
None, max_length=30, description="Responsible RFC"
)
position: Optional[str] = Field(
None, max_length=30, description="Responsible position"
)
# Configuration
logo: Optional[str] = Field(None, max_length=255, description="Company logo")
has_express_line: Optional[bool] = Field(None, description="Has express line")
order_format_type: Optional[str] = Field(None, max_length=19, description="Order format type")
order_format_type: Optional[str] = Field(
None, max_length=19, description="Order format type"
)
previous_code: Optional[int] = Field(None, description="Previous code")
is_service_company: Optional[bool] = Field(None, description="Is service company")
# Client and subassembly
client_name: Optional[str] = Field(None, max_length=300, description="Client name")
subassembly_mode: Optional[str] = Field(None, max_length=7, description="Subassembly mode")
subassembly_mode: Optional[str] = Field(
None, max_length=7, description="Subassembly mode"
)
# Additional information
curp: Optional[str] = Field(None, max_length=19, description="CURP")
inter_db_name: Optional[str] = Field(None, max_length=100, description="Inter DB name")
inter_db_name: Optional[str] = Field(
None, max_length=100, description="Inter DB name"
)
ctpat_svi: Optional[str] = Field(None, max_length=100, description="CTPAT SVI")
trusted_exporter_number: Optional[str] = Field(None, max_length=50, description="Trusted exporter number")
prevalidator_key: Optional[str] = Field(None, max_length=20, description="Prevalidator key")
trusted_exporter_number: Optional[str] = Field(
None, max_length=50, description="Trusted exporter number"
)
prevalidator_key: Optional[str] = Field(
None, max_length=20, description="Prevalidator key"
)
seventh_amendment: Optional[bool] = Field(None, description="Seventh amendment")
class Config:
@@ -58,45 +92,78 @@ class CompanyCreateDTO(BaseModel):
class CompanyUpdateDTO(BaseModel):
"""DTO para actualizar una empresa"""
name: Optional[str] = Field(None, max_length=255, description="Company name")
rfc: Optional[str] = Field(None, max_length=30, description="Company RFC")
main_activity: Optional[str] = Field(None, max_length=255, description="Main activity")
main_activity: Optional[str] = Field(
None, max_length=255, description="Main activity"
)
# Program information
program: Optional[str] = Field(None, max_length=10, description="Program")
program_number: Optional[str] = Field(None, max_length=40, description="Program number")
program_number: Optional[str] = Field(
None, max_length=40, description="Program number"
)
prosec: Optional[int] = Field(None, description="PROSEC")
prosec_authorization: Optional[str] = Field(None, max_length=20, description="PROSEC authorization")
prosec_authorization: Optional[str] = Field(
None, max_length=20, description="PROSEC authorization"
)
# Identifiers
manufacturer_id: Optional[str] = Field(None, max_length=25, description="Manufacturer ID")
broker_company: Optional[str] = Field(None, max_length=10, description="Broker company")
manufacturer_id: Optional[str] = Field(
None, max_length=25, description="Manufacturer ID"
)
broker_company: Optional[str] = Field(
None, max_length=10, description="Broker company"
)
# Responsible person
responsible: Optional[str] = Field(None, max_length=80, description="Responsible person")
responsible_name: Optional[str] = Field(None, max_length=20, description="Responsible first name")
responsible_last_name: Optional[str] = Field(None, max_length=20, description="Responsible last name")
responsible_mother_last_name: Optional[str] = Field(None, max_length=20, description="Responsible mother's last name")
responsible_rfc: Optional[str] = Field(None, max_length=30, description="Responsible RFC")
position: Optional[str] = Field(None, max_length=30, description="Responsible position")
responsible: Optional[str] = Field(
None, max_length=80, description="Responsible person"
)
responsible_name: Optional[str] = Field(
None, max_length=20, description="Responsible first name"
)
responsible_last_name: Optional[str] = Field(
None, max_length=20, description="Responsible last name"
)
responsible_mother_last_name: Optional[str] = Field(
None, max_length=20, description="Responsible mother's last name"
)
responsible_rfc: Optional[str] = Field(
None, max_length=30, description="Responsible RFC"
)
position: Optional[str] = Field(
None, max_length=30, description="Responsible position"
)
# Configuration
logo: Optional[str] = Field(None, max_length=255, description="Company logo")
has_express_line: Optional[bool] = Field(None, description="Has express line")
order_format_type: Optional[str] = Field(None, max_length=19, description="Order format type")
order_format_type: Optional[str] = Field(
None, max_length=19, description="Order format type"
)
previous_code: Optional[int] = Field(None, description="Previous code")
is_service_company: Optional[bool] = Field(None, description="Is service company")
# Client and subassembly
client_name: Optional[str] = Field(None, max_length=300, description="Client name")
subassembly_mode: Optional[str] = Field(None, max_length=7, description="Subassembly mode")
subassembly_mode: Optional[str] = Field(
None, max_length=7, description="Subassembly mode"
)
# Additional information
curp: Optional[str] = Field(None, max_length=19, description="CURP")
inter_db_name: Optional[str] = Field(None, max_length=100, description="Inter DB name")
inter_db_name: Optional[str] = Field(
None, max_length=100, description="Inter DB name"
)
ctpat_svi: Optional[str] = Field(None, max_length=100, description="CTPAT SVI")
trusted_exporter_number: Optional[str] = Field(None, max_length=50, description="Trusted exporter number")
prevalidator_key: Optional[str] = Field(None, max_length=20, description="Prevalidator key")
trusted_exporter_number: Optional[str] = Field(
None, max_length=50, description="Trusted exporter number"
)
prevalidator_key: Optional[str] = Field(
None, max_length=20, description="Prevalidator key"
)
seventh_amendment: Optional[bool] = Field(None, description="Seventh amendment")
class Config:
@@ -105,8 +172,9 @@ class CompanyUpdateDTO(BaseModel):
class CompanyResponseDTO(BaseModel):
"""DTO para respuesta de empresa"""
id: str
consecutive: bool
id: int
tenant_id: int
name: Optional[str] = None
rfc: Optional[str] = None
main_activity: Optional[str] = None
@@ -154,4 +222,3 @@ class CompanyResponseDTO(BaseModel):
class Config:
from_attributes = True

View File

@@ -1,9 +1,20 @@
"""
Modelos ORM para gestión de empresa
"""
from typing import Optional
from datetime import datetime
from sqlalchemy import DateTime, Integer, String, Boolean, SmallInteger, ForeignKey, PrimaryKeyConstraint, ForeignKeyConstraint, UniqueConstraint
from sqlalchemy import (
DateTime,
Integer,
String,
Boolean,
SmallInteger,
ForeignKey,
PrimaryKeyConstraint,
ForeignKeyConstraint,
UniqueConstraint,
)
from sqlalchemy.sql import func
from sqlalchemy.orm import Mapped, mapped_column
from core.database import Base
@@ -13,11 +24,14 @@ class Company(Base):
"""
Modelo para la tabla Company - Información de la empresa
"""
__tablename__ = "company"
__table_args__ = (
PrimaryKeyConstraint('id', name='company_pkey'),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_company_tenant'),
{"schema": "a76"}
PrimaryKeyConstraint("id", name="company_pkey"),
ForeignKeyConstraint(
["tenant_id"], ["a76.tenants.id"], name="fk_company_tenant"
),
{"schema": "a76"},
)
# Primary key
@@ -64,11 +78,15 @@ class Company(Base):
ctpat_svi: Mapped[Optional[str]] = mapped_column(String(100))
trusted_exporter_number: Mapped[Optional[str]] = mapped_column(String(50))
prevalidator_key: Mapped[Optional[str]] = mapped_column(String(20))
seventh_amendment: Mapped[Optional[bool]] = mapped_column(Boolean) # FINALCONTADORAELECTRONICO renombrado
seventh_amendment: Mapped[Optional[bool]] = mapped_column(
Boolean
) # FINALCONTADORAELECTRONICO renombrado
# Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
created_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now()
)
updated_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now(), onupdate=func.now()
)
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)

View File

@@ -1,23 +1,26 @@
"""
Endpoints API para gestión de empresa
"""
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session
from typing import Optional
from core.database import get_core_db
from core.security import get_current_user, has_role
from core.security import get_current_user, has_role, get_tenant_from_token
from .service import CompanyService
from .dto import CompanyCreateDTO, CompanyUpdateDTO, CompanyResponseDTO
router = APIRouter(prefix="/company")
@router.post("/", response_model=CompanyResponseDTO, status_code=status.HTTP_201_CREATED)
@router.post(
"/", response_model=CompanyResponseDTO, status_code=status.HTTP_201_CREATED
)
async def create_company(
company_data: CompanyCreateDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Create a new company in the system
@@ -30,8 +33,7 @@ async def create_company(
@router.get("/", response_model=Optional[CompanyResponseDTO])
async def get_company(
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)
):
"""
Get the registered company information
@@ -45,73 +47,48 @@ async def get_company(
return company
@router.get("/{company_id}", response_model=CompanyResponseDTO)
async def get_company_by_id(
company_id: str,
@router.get("/my-companies", response_model=list[CompanyResponseDTO])
async def get_my_companies(
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Get company by specific ID
Get all companies that belong to the user's tenant
Returns a list of companies associated with the tenant_id from the user's token
"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(
status_code=400,
detail="Tenant ID not found in token"
)
service = CompanyService(db)
company = service.get_company_by_id(company_id)
if not company:
raise HTTPException(status_code=404, detail=f"Company with ID '{company_id}' not found")
return company
companies = service.get_companies_by_tenant(tenant_id)
@router.put("/{company_id}", response_model=CompanyResponseDTO)
async def update_company(
company_id: str,
company_data: CompanyUpdateDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Update company information
"""
service = CompanyService(db)
company = service.update_company(company_id, company_data)
if not company:
raise HTTPException(status_code=404, detail=f"Company with ID '{company_id}' not found")
return company
@router.delete("/{company_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_company(
company_id: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Delete company from the system
Note: This will completely remove the company from the system.
"""
service = CompanyService(db)
if not service.delete_company(company_id):
raise HTTPException(status_code=404, detail=f"Company with ID '{company_id}' not found")
return companies
@router.get("/status/exists", response_model=dict)
async def check_company_exists(
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)
):
"""
Check if a company is registered in the system
"""
service = CompanyService(db)
exists = service.exists_company()
return {"exists": exists, "message": "Company found" if exists else "No company registered"}
return {
"exists": exists,
"message": "Company found" if exists else "No company registered",
}
# Specific endpoints for important fields
@router.get("/info/basic", response_model=dict)
async def get_company_basic_info(
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)
):
"""
Get basic company information (name, RFC, main activity)
@@ -125,14 +102,13 @@ async def get_company_basic_info(
"name": company.name,
"rfc": company.rfc,
"main_activity": company.main_activity,
"logo": company.logo
"logo": company.logo,
}
@router.get("/info/responsible", response_model=dict)
async def get_company_responsible_info(
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)
):
"""
Get company responsible person information
@@ -148,14 +124,13 @@ async def get_company_responsible_info(
"responsible_last_name": company.responsible_last_name,
"responsible_mother_last_name": company.responsible_mother_last_name,
"responsible_rfc": company.responsible_rfc,
"position": company.position
"position": company.position,
}
@router.get("/info/program", response_model=dict)
async def get_company_program_info(
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)
):
"""
Get company program information
@@ -170,7 +145,61 @@ async def get_company_program_info(
"program_number": company.program_number,
"prosec": company.prosec,
"prosec_authorization": company.prosec_authorization,
"manufacturer_id": company.manufacturer_id
"manufacturer_id": company.manufacturer_id,
}
@router.get("/{company_id}", response_model=CompanyResponseDTO)
async def get_company_by_id(
company_id: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
"""
Get company by specific ID
"""
service = CompanyService(db)
company = service.get_company_by_id(company_id)
if not company:
raise HTTPException(
status_code=404, detail=f"Company with ID '{company_id}' not found"
)
return company
@router.put("/{company_id}", response_model=CompanyResponseDTO)
async def update_company(
company_id: str,
company_data: CompanyUpdateDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
"""
Update company information
"""
service = CompanyService(db)
company = service.update_company(company_id, company_data)
if not company:
raise HTTPException(
status_code=404, detail=f"Company with ID '{company_id}' not found"
)
return company
@router.delete("/{company_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_company(
company_id: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user),
):
"""
Delete company from the system
Note: This will completely remove the company from the system.
"""
service = CompanyService(db)
if not service.delete_company(company_id):
raise HTTPException(
status_code=404, detail=f"Company with ID '{company_id}' not found"
)

View File

@@ -1,6 +1,7 @@
"""
Capa de servicio para lógica de negocio de empresa
"""
from sqlalchemy.orm import Session
from sqlalchemy.exc import IntegrityError
from fastapi import HTTPException
@@ -34,9 +35,14 @@ class CompanyService:
"""
try:
# Verificar que no exista ya una empresa (solo puede haber una por el consecutivo único)
existing = self.db.query(Company).filter(Company.consecutive == True).first()
existing = (
self.db.query(Company).filter(Company.consecutive == True).first()
)
if existing:
raise HTTPException(status_code=400, detail="A company is already registered in the system")
raise HTTPException(
status_code=400,
detail="A company is already registered in the system",
)
# Crear empresa
db_company = Company(
@@ -69,7 +75,7 @@ class CompanyService:
ctpat_svi=company_data.ctpat_svi,
trusted_exporter_number=company_data.trusted_exporter_number,
prevalidator_key=company_data.prevalidator_key,
seventh_amendment=company_data.seventh_amendment
seventh_amendment=company_data.seventh_amendment,
)
self.db.add(db_company)
@@ -83,7 +89,10 @@ class CompanyService:
except IntegrityError as e:
self.db.rollback()
logger.error(f"IntegrityError creating company: {str(e)}")
raise HTTPException(status_code=400, detail="Integrity error: A company already exists in the system")
raise HTTPException(
status_code=400,
detail="Integrity error: A company already exists in the system",
)
except HTTPException:
raise
except Exception as e:
@@ -118,7 +127,9 @@ class CompanyService:
return None
return CompanyResponseDTO.model_validate(company)
def update_company(self, company_id: str, company_data: CompanyUpdateDTO) -> Optional[CompanyResponseDTO]:
def update_company(
self, company_id: str, company_data: CompanyUpdateDTO
) -> Optional[CompanyResponseDTO]:
"""
Actualiza una empresa
@@ -179,6 +190,26 @@ class CompanyService:
Returns:
True si existe una empresa, False en caso contrario
"""
return self.db.query(Company).filter(Company.consecutive == True).first() is not None
return (
self.db.query(Company).filter(Company.consecutive == True).first()
is not None
)
def get_companies_by_tenant(self, tenant_id: int) -> List[CompanyResponseDTO]:
"""
Obtiene todas las compañías que pertenecen a un tenant específico
Args:
tenant_id: ID del tenant
Returns:
Lista de CompanyResponseDTO
"""
companies = (
self.db.query(Company)
.filter(Company.tenant_id == tenant_id)
.order_by(Company.name)
.all()
)
return [CompanyResponseDTO.model_validate(company) for company in companies]

View File

@@ -4,15 +4,18 @@ DTOs for CountryRuleOct.
from pydantic import BaseModel
class CountryRuleOctBaseDTO(BaseModel):
permission: str
line: int
fraction: str
country_code: str
class CountryRuleOctCreateDTO(CountryRuleOctBaseDTO):
pass
class CountryRuleOctResponseDTO(CountryRuleOctBaseDTO):
class Config:
from_attributes = True

View File

@@ -1,4 +1,11 @@
from sqlalchemy import Integer, String, ForeignKey, PrimaryKeyConstraint, ForeignKeyConstraint, UniqueConstraint
from sqlalchemy import (
Integer,
String,
ForeignKey,
PrimaryKeyConstraint,
ForeignKeyConstraint,
UniqueConstraint,
)
from sqlalchemy.orm import Mapped, mapped_column
from core.database import Base
@@ -6,17 +13,35 @@ from core.database import Base
class CountryRuleOct(Base):
__tablename__ = "country_rule_oct"
__table_args__ = (
PrimaryKeyConstraint('id', name='country_rule_oct_pkey'),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_country_rule_oct_tenant'),
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_country_rule_oct_company'),
PrimaryKeyConstraint("id", name="country_rule_oct_pkey"),
ForeignKeyConstraint(
['tenant_id', 'company_id', 'permission', 'line', 'fraction'],
['a76.fraction_rule_octave.tenant_id', 'a76.fraction_rule_octave.company_id', 'a76.fraction_rule_octave.permission', 'a76.fraction_rule_octave.line', 'a76.fraction_rule_octave.fraction'],
ondelete="CASCADE",
name='fk_country_rule_oct_frac_octava'
["tenant_id"], ["a76.tenants.id"], name="fk_country_rule_oct_tenant"
),
UniqueConstraint('tenant_id', 'company_id', 'permission', 'line', 'fraction', 'country_code', name='uq_country_rule_oct_permission_line_fraction_country'),
{"schema": "a76"}
ForeignKeyConstraint(
["company_id"], ["a76.company.id"], name="fk_country_rule_oct_company"
),
ForeignKeyConstraint(
["tenant_id", "company_id", "permission", "line", "fraction"],
[
"a76.fraction_rule_octave.tenant_id",
"a76.fraction_rule_octave.company_id",
"a76.fraction_rule_octave.permission",
"a76.fraction_rule_octave.line",
"a76.fraction_rule_octave.fraction",
],
ondelete="CASCADE",
name="fk_country_rule_oct_frac_octava",
),
UniqueConstraint(
"tenant_id",
"company_id",
"permission",
"line",
"fraction",
"country_code",
name="uq_country_rule_oct_permission_line_fraction_country",
),
{"schema": "a76"},
)
id: Mapped[int] = mapped_column(Integer, primary_key=True)
@@ -27,4 +52,3 @@ class CountryRuleOct(Base):
line: Mapped[int] = mapped_column()
fraction: Mapped[str] = mapped_column(String(10))
country_code: Mapped[str] = mapped_column(String(3))

View File

@@ -12,8 +12,7 @@ router = APIRouter(prefix="/country-rule-oct", tags=["CountryRuleOct"])
@router.get("/", response_model=List[CountryRuleOctResponseDTO])
async def list_countries(
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)
):
"""
List all CountryRuleOct entries.
@@ -28,14 +27,17 @@ async def list_countries(
return db.query(CountryRuleOctService).all()
@router.get("/{permission}/{line}/{fraction}/{country_code}", response_model=CountryRuleOctResponseDTO)
@router.get(
"/{permission}/{line}/{fraction}/{country_code}",
response_model=CountryRuleOctResponseDTO,
)
async def read_country_rule(
permission: str,
line: int,
fraction: str,
country_code: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Get a specific CountryRuleOct by its composite key.
@@ -53,11 +55,13 @@ async def read_country_rule(
return country
@router.post("/", response_model=CountryRuleOctResponseDTO, status_code=status.HTTP_201_CREATED)
@router.post(
"/", response_model=CountryRuleOctResponseDTO, status_code=status.HTTP_201_CREATED
)
async def create_country_rule(
country_data: CountryRuleOctCreateDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Create a new CountryRuleOct entry.
@@ -65,18 +69,23 @@ async def create_country_rule(
return CountryRuleOctService.create_country_rule(db, country_data)
@router.delete("/{permission}/{line}/{fraction}/{country_code}", status_code=status.HTTP_204_NO_CONTENT)
@router.delete(
"/{permission}/{line}/{fraction}/{country_code}",
status_code=status.HTTP_204_NO_CONTENT,
)
async def delete_country_rule(
permission: str,
line: int,
fraction: str,
country_code: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Delete a CountryRuleOct by its composite key.
"""
country = CountryRuleOctService.delete_country_rule(db, permission, line, fraction, country_code)
country = CountryRuleOctService.delete_country_rule(
db, permission, line, fraction, country_code
)
if not country:
raise HTTPException(status_code=404, detail="CountryRuleOct not found")

View File

@@ -5,15 +5,22 @@ Service layer for CountryRuleOct.
from sqlalchemy.orm import Session
from . import models, dto
class CountryRuleOctService:
@staticmethod
def get_country_by_keys(db: Session, permission: str, line: int, fraction: str, country_code: str):
return db.query(models.CountryRuleOct).filter(
models.CountryRuleOct.permission == permission,
models.CountryRuleOct.line == line,
models.CountryRuleOct.fraction == fraction,
models.CountryRuleOct.country_code == country_code
).first()
def get_country_by_keys(
db: Session, permission: str, line: int, fraction: str, country_code: str
):
return (
db.query(models.CountryRuleOct)
.filter(
models.CountryRuleOct.permission == permission,
models.CountryRuleOct.line == line,
models.CountryRuleOct.fraction == fraction,
models.CountryRuleOct.country_code == country_code,
)
.first()
)
@staticmethod
def create_country_rule(db: Session, country_data: dto.CountryRuleOctCreateDTO):
@@ -24,8 +31,12 @@ class CountryRuleOctService:
return new_country
@staticmethod
def delete_country_rule(db: Session, permission: str, line: int, fraction: str, country_code: str):
country = CountryRuleOctService.get_country_by_keys(db, permission, line, fraction, country_code)
def delete_country_rule(
db: Session, permission: str, line: int, fraction: str, country_code: str
):
country = CountryRuleOctService.get_country_by_keys(
db, permission, line, fraction, country_code
)
if country:
db.delete(country)
db.commit()

View File

@@ -1,15 +1,18 @@
from pydantic import BaseModel
from typing import Optional
class ExchangeRateBaseDTO(BaseModel):
date: int
value: Optional[float]
local_currency: Optional[str]
foreign_currency: Optional[str]
class ExchangeRateCreateDTO(ExchangeRateBaseDTO):
pass
class ExchangeRateResponseDTO(ExchangeRateBaseDTO):
class Config:
from_attributes = True

View File

@@ -1,6 +1,15 @@
from typing import Optional
from decimal import Decimal
from sqlalchemy import Integer, String, DECIMAL, PrimaryKeyConstraint, DateTime, ForeignKeyConstraint, UniqueConstraint, ForeignKey
from sqlalchemy import (
Integer,
String,
DECIMAL,
PrimaryKeyConstraint,
DateTime,
ForeignKeyConstraint,
UniqueConstraint,
ForeignKey,
)
from sqlalchemy.orm import Mapped, mapped_column
from core.database import Base
@@ -8,11 +17,17 @@ from core.database import Base
class ExchangeRate(Base):
__tablename__ = "exchange_rate"
__table_args__ = (
PrimaryKeyConstraint('id', name='exchange_rate_pkey'),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_exchange_rate_tenant'),
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_exchange_rate_company'),
UniqueConstraint('tenant_id', 'company_id', 'date', name='uq_exchange_rate_date_tenant'),
{"schema": "a76"}
PrimaryKeyConstraint("id", name="exchange_rate_pkey"),
ForeignKeyConstraint(
["tenant_id"], ["a76.tenants.id"], name="fk_exchange_rate_tenant"
),
ForeignKeyConstraint(
["company_id"], ["a76.company.id"], name="fk_exchange_rate_company"
),
UniqueConstraint(
"tenant_id", "company_id", "date", name="uq_exchange_rate_date_tenant"
),
{"schema": "a76"},
)
id: Mapped[int] = mapped_column(Integer, primary_key=True)

View File

@@ -12,8 +12,7 @@ router = APIRouter(prefix="/exchange-rate", tags=["ExchangeRate"])
@router.get("/", response_model=List[ExchangeRateResponseDTO])
async def list_exchange_rates(
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)
):
"""
List all ExchangeRate entries.
@@ -32,7 +31,7 @@ async def list_exchange_rates(
async def read_exchange_rate(
date: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Get a specific ExchangeRate by its date.
@@ -50,11 +49,13 @@ async def read_exchange_rate(
return exchange_rate
@router.post("/", response_model=ExchangeRateResponseDTO, status_code=status.HTTP_201_CREATED)
@router.post(
"/", response_model=ExchangeRateResponseDTO, status_code=status.HTTP_201_CREATED
)
async def create_exchange_rate(
exchange_rate_data: ExchangeRateCreateDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Create a new ExchangeRate entry.
@@ -73,7 +74,7 @@ async def create_exchange_rate(
async def delete_exchange_rate(
date: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Delete an ExchangeRate by its date.

View File

@@ -1,13 +1,20 @@
from sqlalchemy.orm import Session
from . import models, dto
class ExchangeRateService:
@staticmethod
def get_exchange_rate_by_date(db: Session, date: int):
return db.query(models.ExchangeRate).filter(models.ExchangeRate.date == date).first()
return (
db.query(models.ExchangeRate)
.filter(models.ExchangeRate.date == date)
.first()
)
@staticmethod
def create_exchange_rate(db: Session, exchange_rate_data: dto.ExchangeRateCreateDTO):
def create_exchange_rate(
db: Session, exchange_rate_data: dto.ExchangeRateCreateDTO
):
new_exchange_rate = models.ExchangeRate(**exchange_rate_data.dict())
db.add(new_exchange_rate)
db.commit()

View File

@@ -5,6 +5,7 @@ DTOs for FractionRuleOctave.
from pydantic import BaseModel
from typing import Optional
class FractionRuleOctaveBaseDTO(BaseModel):
PERMISSION: str
LINE: int
@@ -16,9 +17,11 @@ class FractionRuleOctaveBaseDTO(BaseModel):
UNIT_COST_ME: Optional[float]
UNIT_MEASURE: Optional[str]
class FractionRuleOctaveCreateDTO(FractionRuleOctaveBaseDTO):
pass
class FractionRuleOctaveResponseDTO(FractionRuleOctaveBaseDTO):
class Config:
from_attributes = True

View File

@@ -1,4 +1,11 @@
from sqlalchemy import Integer, String, ForeignKey, PrimaryKeyConstraint, ForeignKeyConstraint, UniqueConstraint
from sqlalchemy import (
Integer,
String,
ForeignKey,
PrimaryKeyConstraint,
ForeignKeyConstraint,
UniqueConstraint,
)
from sqlalchemy.orm import Mapped, mapped_column
from core.database import Base
@@ -6,11 +13,22 @@ from core.database import Base
class FractionRuleOctave(Base):
__tablename__ = "fraction_rule_octave"
__table_args__ = (
PrimaryKeyConstraint('id', name='fraction_rule_octave_pkey'),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_fraction_rule_octave_tenant'),
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_fraction_rule_octave_company'),
UniqueConstraint('tenant_id', 'company_id', 'permission', 'line', 'fraction', name='uq_fraction_rule_octave_permission_line_fraction'),
{"schema": "a76"}
PrimaryKeyConstraint("id", name="fraction_rule_octave_pkey"),
ForeignKeyConstraint(
["tenant_id"], ["a76.tenants.id"], name="fk_fraction_rule_octave_tenant"
),
ForeignKeyConstraint(
["company_id"], ["a76.company.id"], name="fk_fraction_rule_octave_company"
),
UniqueConstraint(
"tenant_id",
"company_id",
"permission",
"line",
"fraction",
name="uq_fraction_rule_octave_permission_line_fraction",
),
{"schema": "a76"},
)
id: Mapped[int] = mapped_column(Integer, primary_key=True)
@@ -20,4 +38,3 @@ class FractionRuleOctave(Base):
permission: Mapped[str] = mapped_column(String(20))
line: Mapped[int] = mapped_column(Integer)
fraction: Mapped[str] = mapped_column(String(10))

View File

@@ -12,8 +12,7 @@ router = APIRouter(prefix="/fraction_rule_octave", tags=["FractionRuleOctave"])
@router.get("/", response_model=List[FractionRuleOctaveResponseDTO])
async def list_fractions(
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)
):
"""
List all FractionRuleOctave entries.
@@ -28,13 +27,15 @@ async def list_fractions(
return db.query(FractionRuleOctaveService).all()
@router.get("/{permission}/{line}/{fraction}", response_model=FractionRuleOctaveResponseDTO)
@router.get(
"/{permission}/{line}/{fraction}", response_model=FractionRuleOctaveResponseDTO
)
async def read_fraction(
permission: str,
line: int,
fraction: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Get a specific FractionRuleOctave by its composite key.
@@ -52,11 +53,15 @@ async def read_fraction(
return frac
@router.post("/", response_model=FractionRuleOctaveResponseDTO, status_code=status.HTTP_201_CREATED)
@router.post(
"/",
response_model=FractionRuleOctaveResponseDTO,
status_code=status.HTTP_201_CREATED,
)
async def create_frac(
frac_data: FractionRuleOctaveCreateDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Create a new FractionRuleOctave entry.
@@ -71,13 +76,15 @@ async def create_frac(
return FractionRuleOctaveService.create_frac(db, frac_data)
@router.delete("/{permission}/{line}/{fraction}", status_code=status.HTTP_204_NO_CONTENT)
@router.delete(
"/{permission}/{line}/{fraction}", status_code=status.HTTP_204_NO_CONTENT
)
async def delete_fraction(
permission: str,
line: int,
fraction: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Delete a FractionRuleOctave by its composite key.

View File

@@ -5,14 +5,21 @@ from . import models, dto
Service layer for FractionRuleOctave.
"""
class FractionRuleOctaveService:
@staticmethod
def get_fraction_by_permission_line(db: Session, permission: str, line: int, fraction: str):
return db.query(models.FractionRuleOctave).filter(
models.FractionRuleOctave.permission == permission,
models.FractionRuleOctave.line == line,
models.FractionRuleOctave.fraction == fraction
).first()
def get_fraction_by_permission_line(
db: Session, permission: str, line: int, fraction: str
):
return (
db.query(models.FractionRuleOctave)
.filter(
models.FractionRuleOctave.permission == permission,
models.FractionRuleOctave.line == line,
models.FractionRuleOctave.fraction == fraction,
)
.first()
)
@staticmethod
def create_frac(db: Session, frac_data: dto.FractionRuleOctaveCreateDTO):
@@ -24,7 +31,9 @@ class FractionRuleOctaveService:
@staticmethod
def delete_fraction(db: Session, permission: str, line: int, fraction: str):
frac = FractionRuleOctaveService.get_fraction_by_permission_line(db, permission, line, fraction)
frac = FractionRuleOctaveService.get_fraction_by_permission_line(
db, permission, line, fraction
)
if frac:
db.delete(frac)
db.commit()

View File

@@ -1,6 +1,7 @@
"""
Módulo de Licenses
"""
from .routes import router
__all__ = ["router"]

View File

@@ -1,6 +1,7 @@
"""
DTOs para módulo de licencias
"""
from pydantic import BaseModel, Field
from typing import Optional
from datetime import datetime
@@ -9,6 +10,7 @@ from enum import Enum
class LicensePlanDTO(str, Enum):
"""Planes de licencia"""
FREE = "free"
BASIC = "basic"
PROFESSIONAL = "professional"
@@ -17,6 +19,7 @@ class LicensePlanDTO(str, Enum):
class LicenseStatusDTO(str, Enum):
"""Estados de licencia"""
ACTIVE = "active"
EXPIRED = "expired"
SUSPENDED = "suspended"
@@ -26,11 +29,16 @@ class LicenseStatusDTO(str, Enum):
class LicenseCreateDTO(BaseModel):
"""DTO para crear una nueva licencia"""
tenant_id: int = Field(..., description="ID del tenant")
plan: LicensePlanDTO = Field(..., description="Plan de licencia")
max_users: int = Field(default=5, ge=1, description="Número máximo de usuarios")
max_storage_gb: int = Field(default=10, ge=1, description="Almacenamiento máximo en GB")
max_monthly_operations: int = Field(default=1000, ge=1, description="Operaciones mensuales máximas")
max_storage_gb: int = Field(
default=10, ge=1, description="Almacenamiento máximo en GB"
)
max_monthly_operations: int = Field(
default=1000, ge=1, description="Operaciones mensuales máximas"
)
feature_api_access: bool = Field(default=True)
feature_advanced_reports: bool = Field(default=False)
@@ -53,13 +61,14 @@ class LicenseCreateDTO(BaseModel):
"feature_integrations": True,
"feature_dedicated_support": False,
"starts_at": "2025-01-01T00:00:00Z",
"expires_at": "2025-12-31T23:59:59Z"
"expires_at": "2025-12-31T23:59:59Z",
}
}
class LicenseUpdateDTO(BaseModel):
"""DTO para actualizar una licencia"""
plan: Optional[LicensePlanDTO] = None
status: Optional[LicenseStatusDTO] = None
max_users: Optional[int] = Field(None, ge=1)
@@ -76,6 +85,7 @@ class LicenseUpdateDTO(BaseModel):
class LicenseResponseDTO(BaseModel):
"""DTO para respuesta de licencia"""
id: int
tenant_id: int
plan: LicensePlanDTO
@@ -101,6 +111,7 @@ class LicenseResponseDTO(BaseModel):
class LicenseValidationResponseDTO(BaseModel):
"""DTO para respuesta de validación de licencia"""
is_valid: bool
status: LicenseStatusDTO
plan: LicensePlanDTO
@@ -114,13 +125,14 @@ class LicenseValidationResponseDTO(BaseModel):
"status": "active",
"plan": "professional",
"expires_at": "2025-12-31T23:59:59Z",
"reason": None
"reason": None,
}
}
class LicenseUsageResponseDTO(BaseModel):
"""DTO para respuesta de uso de licencia"""
tenant_id: int
period_start: datetime
period_end: datetime

View File

@@ -1,8 +1,17 @@
"""
Modelos ORM para gestión de licencias
"""
from datetime import datetime
from sqlalchemy import Column, Integer, String, DateTime, Boolean, ForeignKey, Enum as SQLEnum
from sqlalchemy import (
Column,
Integer,
String,
DateTime,
Boolean,
ForeignKey,
Enum as SQLEnum,
)
from sqlalchemy.sql import func
from sqlalchemy.orm import relationship
from sqlalchemy.orm import Mapped, mapped_column
@@ -12,6 +21,7 @@ import enum
class LicensePlan(enum.Enum):
"""Planes de licencia disponibles"""
FREE = "free"
BASIC = "basic"
PROFESSIONAL = "professional"
@@ -20,6 +30,7 @@ class LicensePlan(enum.Enum):
class LicenseStatus(enum.Enum):
"""Estados de licencia"""
ACTIVE = "active"
EXPIRED = "expired"
SUSPENDED = "suspended"
@@ -31,15 +42,20 @@ class License(Base):
"""
Modelo de Licencia - Control de planes y límites por tenant
"""
__tablename__ = "licenses"
__table_args__ = {"schema": "a76"}
id = Column(Integer, primary_key=True, index=True)
tenant_id = Column(Integer, ForeignKey("a76.tenants.id"), nullable=False, unique=True, index=True)
tenant_id = Column(
Integer, ForeignKey("a76.tenants.id"), nullable=False, unique=True, index=True
)
# Plan y características
plan = Column(SQLEnum(LicensePlan), default=LicensePlan.FREE, nullable=False)
status = Column(SQLEnum(LicenseStatus), default=LicenseStatus.PENDING, nullable=False)
status = Column(
SQLEnum(LicenseStatus), default=LicenseStatus.PENDING, nullable=False
)
# Límites del plan
max_users = Column(Integer, default=5, nullable=False)
@@ -57,8 +73,12 @@ class License(Base):
expires_at = Column(DateTime(timezone=True), nullable=False)
# Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
created_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now()
)
updated_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now(), onupdate=func.now()
)
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
def __repr__(self):
@@ -69,11 +89,14 @@ class LicenseUsage(Base):
"""
Modelo para tracking de uso de licencia
"""
__tablename__ = "license_usage"
__table_args__ = {"schema": "a76"}
id = Column(Integer, primary_key=True, index=True)
tenant_id = Column(Integer, ForeignKey("a76.tenants.id"), nullable=False, index=True)
tenant_id = Column(
Integer, ForeignKey("a76.tenants.id"), nullable=False, index=True
)
# Métricas de uso
period_start = Column(DateTime(timezone=True), nullable=False)
@@ -85,8 +108,12 @@ class LicenseUsage(Base):
api_calls_count = Column(Integer, default=0)
# Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
created_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now()
)
updated_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now(), onupdate=func.now()
)
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
def __repr__(self):

View File

@@ -1,6 +1,7 @@
"""
Endpoints API para gestión de licencias
"""
from fastapi import APIRouter, Depends, HTTPException, Request
from sqlalchemy.orm import Session
@@ -11,7 +12,7 @@ from .dto import (
LicenseUpdateDTO,
LicenseResponseDTO,
LicenseValidationResponseDTO,
LicenseUsageResponseDTO
LicenseUsageResponseDTO,
)
from .service import LicenseService
@@ -22,7 +23,7 @@ router = APIRouter(prefix="/licenses")
async def create_license(
license_data: LicenseCreateDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
"""
Crea una nueva licencia para un tenant
@@ -37,7 +38,7 @@ async def create_license(
async def get_license_by_tenant(
tenant_id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Obtiene la licencia de un tenant específico
@@ -54,7 +55,7 @@ async def update_license(
tenant_id: int,
license_data: LicenseUpdateDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(has_role("admin"))
current_user: dict = Depends(has_role("admin")),
):
"""
Actualiza la licencia de un tenant
@@ -72,7 +73,7 @@ async def update_license(
async def validate_license(
tenant_id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Valida si la licencia de un tenant está activa y vigente
@@ -86,7 +87,7 @@ async def validate_license(
async def get_license_usage(
tenant_id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Obtiene el uso actual de la licencia de un tenant
@@ -102,7 +103,7 @@ async def get_license_usage(
async def get_my_license(
request: Request,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Obtiene la licencia del tenant del usuario actual

View File

@@ -1,6 +1,7 @@
"""
Servicio de lógica de negocio para licencias
"""
from sqlalchemy.orm import Session
from sqlalchemy.exc import IntegrityError
from fastapi import HTTPException
@@ -14,7 +15,7 @@ from .dto import (
LicenseUpdateDTO,
LicenseResponseDTO,
LicenseValidationResponseDTO,
LicenseUsageResponseDTO
LicenseUsageResponseDTO,
)
logger = logging.getLogger(__name__)
@@ -41,14 +42,16 @@ class LicenseService:
"""
try:
# Verificar que el tenant no tenga ya una licencia
existing = self.db.query(License).filter(
License.tenant_id == license_data.tenant_id
).first()
existing = (
self.db.query(License)
.filter(License.tenant_id == license_data.tenant_id)
.first()
)
if existing:
raise HTTPException(
status_code=400,
detail=f"Tenant {license_data.tenant_id} already has a license"
detail=f"Tenant {license_data.tenant_id} already has a license",
)
# Crear licencia
@@ -64,7 +67,7 @@ class LicenseService:
feature_integrations=license_data.feature_integrations,
feature_dedicated_support=license_data.feature_dedicated_support,
starts_at=license_data.starts_at,
expires_at=license_data.expires_at
expires_at=license_data.expires_at,
)
self.db.add(db_license)
@@ -101,7 +104,9 @@ class LicenseService:
return None
return LicenseResponseDTO.model_validate(license)
def update_license(self, tenant_id: int, license_data: LicenseUpdateDTO) -> Optional[LicenseResponseDTO]:
def update_license(
self, tenant_id: int, license_data: LicenseUpdateDTO
) -> Optional[LicenseResponseDTO]:
"""
Actualiza una licencia
@@ -152,7 +157,7 @@ class LicenseService:
"status": "not_found",
"plan": None,
"expires_at": None,
"reason": "License not found"
"reason": "License not found",
}
now = datetime.now(timezone.utc)
@@ -164,7 +169,7 @@ class LicenseService:
"status": license.status.value,
"plan": license.plan.value,
"expires_at": license.expires_at,
"reason": f"License status is {license.status.value}"
"reason": f"License status is {license.status.value}",
}
# Verificar vigencia
@@ -178,7 +183,7 @@ class LicenseService:
"status": "expired",
"plan": license.plan.value,
"expires_at": license.expires_at,
"reason": "License has expired"
"reason": "License has expired",
}
# Licencia válida
@@ -187,7 +192,7 @@ class LicenseService:
"status": license.status.value,
"plan": license.plan.value,
"expires_at": license.expires_at,
"reason": None
"reason": None,
}
def get_usage(self, tenant_id: int) -> Optional[LicenseUsageResponseDTO]:
@@ -205,9 +210,12 @@ class LicenseService:
return None
# Obtener último registro de uso
usage = self.db.query(LicenseUsage).filter(
LicenseUsage.tenant_id == tenant_id
).order_by(LicenseUsage.created_at.desc()).first()
usage = (
self.db.query(LicenseUsage)
.filter(LicenseUsage.tenant_id == tenant_id)
.order_by(LicenseUsage.created_at.desc())
.first()
)
if not usage:
# Crear registro inicial si no existe
@@ -218,13 +226,25 @@ class LicenseService:
active_users=0,
storage_used_gb=0,
operations_count=0,
api_calls_count=0
api_calls_count=0,
)
# Calcular porcentajes
users_usage = (usage.active_users / license.max_users * 100) if license.max_users > 0 else 0
storage_usage = (usage.storage_used_gb / license.max_storage_gb * 100) if license.max_storage_gb > 0 else 0
operations_usage = (usage.operations_count / license.max_monthly_operations * 100) if license.max_monthly_operations > 0 else 0
users_usage = (
(usage.active_users / license.max_users * 100)
if license.max_users > 0
else 0
)
storage_usage = (
(usage.storage_used_gb / license.max_storage_gb * 100)
if license.max_storage_gb > 0
else 0
)
operations_usage = (
(usage.operations_count / license.max_monthly_operations * 100)
if license.max_monthly_operations > 0
else 0
)
return LicenseUsageResponseDTO(
tenant_id=tenant_id,
@@ -239,5 +259,5 @@ class LicenseService:
max_monthly_operations=license.max_monthly_operations,
users_usage_percent=round(users_usage, 2),
storage_usage_percent=round(storage_usage, 2),
operations_usage_percent=round(operations_usage, 2)
operations_usage_percent=round(operations_usage, 2),
)

View File

@@ -5,6 +5,7 @@ DTOs for GBultos.
from pydantic import BaseModel
from typing import Optional
class GBultoBaseDTO(BaseModel):
CODE: str
DESCRIPTION: Optional[str]
@@ -15,9 +16,11 @@ class GBultoBaseDTO(BaseModel):
CODE_ACE: Optional[str]
CODE_AAMEX: Optional[str]
class GBultoCreateDTO(GBultoBaseDTO):
pass
class GBultoUpdateDTO(BaseModel):
DESCRIPTION: Optional[str]
DESCRIPTIONI: Optional[str]
@@ -27,6 +30,7 @@ class GBultoUpdateDTO(BaseModel):
CODE_ACE: Optional[str]
CODE_AAMEX: Optional[str]
class GBultoResponseDTO(GBultoBaseDTO):
CREATED_AT: Optional[str]
UPDATED_AT: Optional[str]

View File

@@ -1,7 +1,16 @@
from typing import Optional
from datetime import datetime
from decimal import Decimal
from sqlalchemy import DateTime, Integer, String, DECIMAL, ForeignKey, PrimaryKeyConstraint, ForeignKeyConstraint, UniqueConstraint
from sqlalchemy import (
DateTime,
Integer,
String,
DECIMAL,
ForeignKey,
PrimaryKeyConstraint,
ForeignKeyConstraint,
UniqueConstraint,
)
from sqlalchemy.sql import func
from sqlalchemy.orm import Mapped, mapped_column
from core.database import Base
@@ -10,11 +19,15 @@ from core.database import Base
class Package(Base):
__tablename__ = "packages" # GBultos
__table_args__ = (
PrimaryKeyConstraint('id', name='packages_pkey'),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_packages_tenant'),
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_packages_company'),
UniqueConstraint('tenant_id', 'company_id', 'key', name='packages_key_ukey'),
{"schema": "a76"}
PrimaryKeyConstraint("id", name="packages_pkey"),
ForeignKeyConstraint(
["tenant_id"], ["a76.tenants.id"], name="fk_packages_tenant"
),
ForeignKeyConstraint(
["company_id"], ["a76.company.id"], name="fk_packages_company"
),
UniqueConstraint("tenant_id", "company_id", "key", name="packages_key_ukey"),
{"schema": "a76"},
)
id: Mapped[int] = mapped_column(Integer, primary_key=True)
@@ -31,8 +44,10 @@ class Package(Base):
code_aamex: Mapped[Optional[str]] = mapped_column(String(9))
# Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
created_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now()
)
updated_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now(), onupdate=func.now()
)
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)

View File

@@ -16,7 +16,7 @@ async def list_bultos(
skip: int = 0,
limit: int = 100,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
List all GBultos with pagination.
@@ -35,7 +35,7 @@ async def list_bultos(
async def read_bulto(
code: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Get a specific Package by its CODE.
@@ -57,7 +57,7 @@ async def read_bulto(
async def create_gbulto(
bulto_data: GBultoCreateDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Create a new Package.
@@ -77,7 +77,7 @@ async def update_bulto(
code: str,
bulto_data: GBultoUpdateDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Update an existing Package.
@@ -99,7 +99,7 @@ async def update_bulto(
async def delete_bulto(
code: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Delete a Package by its CODE.

View File

@@ -1,6 +1,7 @@
from sqlalchemy.orm import Session
from . import models, dto
class GBultoService:
"""
Service layer for GBultos.

View File

@@ -1,6 +1,7 @@
"""
Módulo de GParts
"""
from .routes import router
__all__ = ["router"]

View File

@@ -2,6 +2,7 @@
DTOs (Data Transfer Objects) para módulo de partes/componentes
Reemplaza schemas.py siguiendo enfoque DDD y estilo NestJS
"""
from pydantic import BaseModel, Field
from typing import Optional
from datetime import datetime
@@ -10,19 +11,32 @@ from decimal import Decimal
class PartCreateDTO(BaseModel):
"""DTO para crear una parte"""
client_id: int = Field(..., description="Client key")
part_number: str = Field(..., max_length=49, description="Part number")
fraction: Optional[str] = Field(None, max_length=10, description="Tariff fraction")
description_spanish: Optional[str] = Field(None, max_length=500, description="Description in Spanish")
description_english: Optional[str] = Field(None, max_length=500, description="Description in English")
description_spanish: Optional[str] = Field(
None, max_length=500, description="Description in Spanish"
)
description_english: Optional[str] = Field(
None, max_length=500, description="Description in English"
)
part_class: Optional[str] = Field(None, max_length=8, description="Part class")
unit_of_measure: Optional[str] = Field(None, max_length=5, description="Unit of measure")
commercial_part_number: Optional[str] = Field(None, max_length=70, description="Commercial part number")
country_of_origin: Optional[str] = Field(None, max_length=3, description="Country of origin code")
unit_of_measure: Optional[str] = Field(
None, max_length=5, description="Unit of measure"
)
commercial_part_number: Optional[str] = Field(
None, max_length=70, description="Commercial part number"
)
country_of_origin: Optional[str] = Field(
None, max_length=3, description="Country of origin code"
)
# Pricing and currency
unit_cost: Optional[Decimal] = Field(None, description="Unit cost")
currency_type: Optional[str] = Field(None, max_length=2, description="Currency type")
currency_type: Optional[str] = Field(
None, max_length=2, description="Currency type"
)
currency_key: Optional[str] = Field(None, max_length=3, description="Currency key")
# Weight information
@@ -30,23 +44,33 @@ class PartCreateDTO(BaseModel):
weight_type: Optional[str] = Field(None, max_length=6, description="Weight type")
# Classification and regulatory
us_fraction: Optional[str] = Field(None, max_length=16, description="US tariff fraction")
us_fraction: Optional[str] = Field(
None, max_length=16, description="US tariff fraction"
)
fda_key: Optional[str] = Field(None, max_length=20, description="FDA key")
fcc_key: Optional[str] = Field(None, max_length=30, description="FCC key")
license_code: Optional[str] = Field(None, max_length=3, description="License code")
eccn: Optional[str] = Field(None, max_length=20, description="Export Control Classification Number")
eccn: Optional[str] = Field(
None, max_length=20, description="Export Control Classification Number"
)
export_code: Optional[str] = Field(None, max_length=2, description="Export code")
exclusion_symbol: Optional[str] = Field(None, max_length=19, description="Exclusion symbol")
exclusion_symbol: Optional[str] = Field(
None, max_length=19, description="Exclusion symbol"
)
# Additional information
supplier: Optional[str] = Field(None, max_length=14, description="Supplier")
alternate_unit_measure: Optional[str] = Field(None, max_length=14, description="Alternate unit of measure")
alternate_unit_measure: Optional[str] = Field(
None, max_length=14, description="Alternate unit of measure"
)
added_value: Optional[Decimal] = Field(None, description="Added value")
# Status and media
enabled_disabled: Optional[int] = Field(None, description="Enabled/Disabled status")
creation_date: Optional[int] = Field(None, description="Creation date")
part_photo: Optional[str] = Field(None, max_length=255, description="Part photo URL")
part_photo: Optional[str] = Field(
None, max_length=255, description="Part photo URL"
)
class Config:
from_attributes = True
@@ -54,17 +78,30 @@ class PartCreateDTO(BaseModel):
class PartUpdateDTO(BaseModel):
"""DTO para actualizar una parte"""
fraction: Optional[str] = Field(None, max_length=10, description="Tariff fraction")
description_spanish: Optional[str] = Field(None, max_length=500, description="Description in Spanish")
description_english: Optional[str] = Field(None, max_length=500, description="Description in English")
description_spanish: Optional[str] = Field(
None, max_length=500, description="Description in Spanish"
)
description_english: Optional[str] = Field(
None, max_length=500, description="Description in English"
)
part_class: Optional[str] = Field(None, max_length=8, description="Part class")
unit_of_measure: Optional[str] = Field(None, max_length=5, description="Unit of measure")
commercial_part_number: Optional[str] = Field(None, max_length=70, description="Commercial part number")
country_of_origin: Optional[str] = Field(None, max_length=3, description="Country of origin code")
unit_of_measure: Optional[str] = Field(
None, max_length=5, description="Unit of measure"
)
commercial_part_number: Optional[str] = Field(
None, max_length=70, description="Commercial part number"
)
country_of_origin: Optional[str] = Field(
None, max_length=3, description="Country of origin code"
)
# Pricing and currency
unit_cost: Optional[Decimal] = Field(None, description="Unit cost")
currency_type: Optional[str] = Field(None, max_length=2, description="Currency type")
currency_type: Optional[str] = Field(
None, max_length=2, description="Currency type"
)
currency_key: Optional[str] = Field(None, max_length=3, description="Currency key")
# Weight information
@@ -72,22 +109,32 @@ class PartUpdateDTO(BaseModel):
weight_type: Optional[str] = Field(None, max_length=6, description="Weight type")
# Classification and regulatory
us_fraction: Optional[str] = Field(None, max_length=16, description="US tariff fraction")
us_fraction: Optional[str] = Field(
None, max_length=16, description="US tariff fraction"
)
fda_key: Optional[str] = Field(None, max_length=20, description="FDA key")
fcc_key: Optional[str] = Field(None, max_length=30, description="FCC key")
license_code: Optional[str] = Field(None, max_length=3, description="License code")
eccn: Optional[str] = Field(None, max_length=20, description="Export Control Classification Number")
eccn: Optional[str] = Field(
None, max_length=20, description="Export Control Classification Number"
)
export_code: Optional[str] = Field(None, max_length=2, description="Export code")
exclusion_symbol: Optional[str] = Field(None, max_length=19, description="Exclusion symbol")
exclusion_symbol: Optional[str] = Field(
None, max_length=19, description="Exclusion symbol"
)
# Additional information
supplier: Optional[str] = Field(None, max_length=14, description="Supplier")
alternate_unit_measure: Optional[str] = Field(None, max_length=14, description="Alternate unit of measure")
alternate_unit_measure: Optional[str] = Field(
None, max_length=14, description="Alternate unit of measure"
)
added_value: Optional[Decimal] = Field(None, description="Added value")
# Status and media
enabled_disabled: Optional[int] = Field(None, description="Enabled/Disabled status")
part_photo: Optional[str] = Field(None, max_length=255, description="Part photo URL")
part_photo: Optional[str] = Field(
None, max_length=255, description="Part photo URL"
)
class Config:
from_attributes = True
@@ -95,6 +142,7 @@ class PartUpdateDTO(BaseModel):
class PartResponseDTO(BaseModel):
"""DTO para respuesta de parte"""
client_id: int
part_number: str
fraction: Optional[str] = None
@@ -143,6 +191,7 @@ class PartResponseDTO(BaseModel):
class PartBasicDTO(BaseModel):
"""DTO para información básica de parte"""
client_id: int
part_number: str
description_spanish: Optional[str] = None
@@ -158,6 +207,7 @@ class PartBasicDTO(BaseModel):
class PartListDTO(BaseModel):
"""DTO para lista de partes"""
parts: list[PartBasicDTO]
total: int
page: int
@@ -169,6 +219,7 @@ class PartListDTO(BaseModel):
class PartSearchDTO(BaseModel):
"""DTO para búsqueda de partes"""
client_id: Optional[int] = Field(None, description="Filter by client key")
part_number: Optional[str] = Field(None, description="Search by part number")
description: Optional[str] = Field(None, description="Search in descriptions")
@@ -178,5 +229,3 @@ class PartSearchDTO(BaseModel):
class Config:
from_attributes = True

View File

@@ -1,10 +1,20 @@
"""
Modelos ORM para gestión de partes/componentes
"""
from typing import TYPE_CHECKING, Optional
from datetime import datetime
from decimal import Decimal
from sqlalchemy import Integer, String, Numeric, SmallInteger, ForeignKey, PrimaryKeyConstraint, ForeignKeyConstraint, UniqueConstraint
from sqlalchemy import (
Integer,
String,
Numeric,
SmallInteger,
ForeignKey,
PrimaryKeyConstraint,
ForeignKeyConstraint,
UniqueConstraint,
)
from sqlalchemy.sql import func
from sqlalchemy.orm import Mapped, mapped_column, relationship
from core.database import Base
@@ -19,15 +29,24 @@ class Part(Base):
"""
Modelo para la tabla GPartes - Información de partes en los sistemas SCAII (N), SCAF (S) Y WINSAAI (W)
"""
__tablename__ = "parts"
__table_args__ = (
PrimaryKeyConstraint('id', name='parts_pkey'),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_parts_tenant'),
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_parts_company'),
ForeignKeyConstraint(['country_of_origin'], ['public.countries.m3_key'], name='fk_parts_country'),
ForeignKeyConstraint(['currency_key'], ['public.currency_types.code'], name='fk_parts_currency'),
UniqueConstraint('tenant_id', 'company_id', 'part_number', name='client_part_ukey'),
{"schema": "a76"}
PrimaryKeyConstraint("id", name="parts_pkey"),
ForeignKeyConstraint(["tenant_id"], ["a76.tenants.id"], name="fk_parts_tenant"),
ForeignKeyConstraint(
["company_id"], ["a76.company.id"], name="fk_parts_company"
),
ForeignKeyConstraint(
["country_of_origin"], ["public.countries.m3_key"], name="fk_parts_country"
),
ForeignKeyConstraint(
["currency_key"], ["public.currency_types.code"], name="fk_parts_currency"
),
UniqueConstraint(
"tenant_id", "company_id", "part_number", name="client_part_ukey"
),
{"schema": "a76"},
)
id: Mapped[int] = mapped_column(Integer, primary_key=True)
@@ -61,7 +80,9 @@ class Part(Base):
fda_key: Mapped[Optional[str]] = mapped_column(String(20))
fcc_key: Mapped[Optional[str]] = mapped_column(String(30))
license_code: Mapped[Optional[str]] = mapped_column(String(3))
eccn: Mapped[Optional[str]] = mapped_column(String(20)) # Export Control Classification Number
eccn: Mapped[Optional[str]] = mapped_column(
String(20)
) # Export Control Classification Number
export_code: Mapped[Optional[str]] = mapped_column(String(2))
exclusion_symbol: Mapped[Optional[str]] = mapped_column(String(19)) # SIMBOLOEXCLIC
@@ -74,14 +95,20 @@ class Part(Base):
enabled_disabled: Mapped[Optional[int]] = mapped_column(SmallInteger)
creation_date: Mapped[Optional[int]] = mapped_column() # FECHACREACIONPARTE
modification_date: Mapped[Optional[int]] = mapped_column() # FECHAMODIFICA
modification_date_iso: Mapped[Optional[datetime]] = mapped_column() # FECHAMODIFICA_ISO
modification_date_iso: Mapped[Optional[datetime]] = (
mapped_column()
) # FECHAMODIFICA_ISO
# Media
part_photo: Mapped[Optional[str]] = mapped_column(String(255))
# Relationships
country: Mapped[Optional["Country"]] = relationship(foreign_keys=[country_of_origin])
currency: Mapped[Optional["CurrencyType"]] = relationship(foreign_keys=[currency_key])
country: Mapped[Optional["Country"]] = relationship(
foreign_keys=[country_of_origin]
)
currency: Mapped[Optional["CurrencyType"]] = relationship(
foreign_keys=[currency_key]
)
# Relationship with Class through composite foreign key
# Note: This requires both client_id and part_class to match client_id and class_code in Class
@@ -89,10 +116,8 @@ class Part(Base):
primaryjoin="and_(Part.client_id == Class.client_id, Part.part_class == Class.class_code)",
foreign_keys="[Part.client_id, Part.part_class]",
viewonly=True,
back_populates="parts"
back_populates="parts",
)
def __repr__(self) -> str:
return f"<Part(client_id={self.client_id}, part_number='{self.part_number}', description='{self.description_spanish}')>"

View File

@@ -1,6 +1,7 @@
"""
Endpoints API para gestión de partes/componentes
"""
from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy.orm import Session
from typing import List, Optional
@@ -14,7 +15,7 @@ from .dto import (
PartResponseDTO,
PartBasicDTO,
PartListDTO,
PartSearchDTO
PartSearchDTO,
)
router = APIRouter(prefix="/parts")
@@ -24,7 +25,7 @@ router = APIRouter(prefix="/parts")
async def create_part(
part_data: PartCreateDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Create a new part in the system
@@ -43,7 +44,9 @@ async def create_part(
@router.get("/", response_model=PartListDTO)
async def list_parts(
skip: int = Query(0, ge=0, description="Number of records to skip"),
limit: int = Query(100, ge=1, le=1000, description="Maximum number of records to return"),
limit: int = Query(
100, ge=1, le=1000, description="Maximum number of records to return"
),
client_id: Optional[int] = Query(None, description="Filter by client key"),
part_number: Optional[str] = Query(None, description="Search by part number"),
description: Optional[str] = Query(None, description="Search in descriptions"),
@@ -51,7 +54,7 @@ async def list_parts(
supplier: Optional[str] = Query(None, description="Filter by supplier"),
enabled_only: bool = Query(False, description="Show only enabled parts"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
List parts with optional filters and pagination
@@ -70,7 +73,7 @@ async def list_parts(
description=description,
fraction=fraction,
supplier=supplier,
enabled_only=enabled_only
enabled_only=enabled_only,
)
return service.list_parts(skip, limit, search_params)
@@ -81,7 +84,7 @@ async def get_parts_by_client(
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Get all parts for a specific client
@@ -101,7 +104,7 @@ async def get_parts_by_client(
async def search_by_fraction(
fraction: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Search parts by tariff fraction
@@ -121,7 +124,7 @@ async def search_by_fraction(
async def search_by_supplier(
supplier: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Search parts by supplier
@@ -141,7 +144,7 @@ async def search_by_supplier(
async def get_parts_by_country(
country_code: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Get parts by country of origin
@@ -159,8 +162,7 @@ async def get_parts_by_country(
@router.get("/statistics", response_model=dict)
async def get_parts_statistics(
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)
):
"""
Get basic parts statistics
@@ -181,7 +183,7 @@ async def get_part(
client_id: int,
part_number: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Get part by composite key (client_id + part_number)
@@ -198,7 +200,7 @@ async def get_part(
if not part:
raise HTTPException(
status_code=404,
detail=f"Part with client_id '{client_id}' and part_number '{part_number}' not found"
detail=f"Part with client_id '{client_id}' and part_number '{part_number}' not found",
)
return part
@@ -209,7 +211,7 @@ async def update_part(
part_number: str,
part_data: PartUpdateDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Update part information
@@ -226,7 +228,7 @@ async def update_part(
if not part:
raise HTTPException(
status_code=404,
detail=f"Part with client_id '{client_id}' and part_number '{part_number}' not found"
detail=f"Part with client_id '{client_id}' and part_number '{part_number}' not found",
)
return part
@@ -236,7 +238,7 @@ async def delete_part(
client_id: int,
part_number: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Delete part from the system
@@ -254,16 +256,18 @@ async def delete_part(
if not service.delete_part(client_id, part_number):
raise HTTPException(
status_code=404,
detail=f"Part with client_id '{client_id}' and part_number '{part_number}' not found"
detail=f"Part with client_id '{client_id}' and part_number '{part_number}' not found",
)
@router.patch("/{client_id}/{part_number}/toggle-status", response_model=PartResponseDTO)
@router.patch(
"/{client_id}/{part_number}/toggle-status", response_model=PartResponseDTO
)
async def toggle_part_status(
client_id: int,
part_number: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Toggle part enabled/disabled status
@@ -280,7 +284,7 @@ async def toggle_part_status(
if not part:
raise HTTPException(
status_code=404,
detail=f"Part with client_id '{client_id}' and part_number '{part_number}' not found"
detail=f"Part with client_id '{client_id}' and part_number '{part_number}' not found",
)
return part
@@ -291,7 +295,7 @@ async def get_part_basic_info(
client_id: int,
part_number: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Get basic information for a part
@@ -308,7 +312,7 @@ async def get_part_basic_info(
if not part:
raise HTTPException(
status_code=404,
detail=f"Part with client_id '{client_id}' and part_number '{part_number}' not found"
detail=f"Part with client_id '{client_id}' and part_number '{part_number}' not found",
)
return PartBasicDTO(
@@ -319,7 +323,7 @@ async def get_part_basic_info(
part_class=part.part_class,
unit_cost=part.unit_cost,
currency_key=part.currency_key,
enabled_disabled=part.enabled_disabled
enabled_disabled=part.enabled_disabled,
)
@@ -328,7 +332,7 @@ async def get_part_regulatory_info(
client_id: int,
part_number: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user),
):
"""
Get regulatory information for a part (FDA, FCC, ECCN, etc.)
@@ -345,7 +349,7 @@ async def get_part_regulatory_info(
if not part:
raise HTTPException(
status_code=404,
detail=f"Part with client_id '{client_id}' and part_number '{part_number}' not found"
detail=f"Part with client_id '{client_id}' and part_number '{part_number}' not found",
)
return {
@@ -358,7 +362,5 @@ async def get_part_regulatory_info(
"license_code": part.license_code,
"eccn": part.eccn,
"export_code": part.export_code,
"exclusion_symbol": part.exclusion_symbol
"exclusion_symbol": part.exclusion_symbol,
}

View File

@@ -1,6 +1,7 @@
"""
Capa de servicio para lógica de negocio de partes/componentes
"""
from sqlalchemy.orm import Session
from sqlalchemy.exc import IntegrityError
from sqlalchemy import or_, and_, func
@@ -34,7 +35,10 @@ class PartService:
except IntegrityError as e:
db.rollback()
logger.error(f"Error creating part: {e}")
raise HTTPException(status_code=400, detail="Part with this client_id and part_number already exists")
raise HTTPException(
status_code=400,
detail="Part with this client_id and part_number already exists",
)
except Exception as e:
db.rollback()
logger.error(f"Unexpected error creating part: {e}")
@@ -46,12 +50,13 @@ class PartService:
Obtener una parte por clave de cliente y número de parte
"""
try:
return db.query(Part).filter(
and_(
Part.client_id == client_id,
Part.part_number == part_number
return (
db.query(Part)
.filter(
and_(Part.client_id == client_id, Part.part_number == part_number)
)
).first()
.first()
)
except Exception as e:
logger.error(f"Error getting part: {e}")
raise HTTPException(status_code=500, detail="Error retrieving part")
@@ -64,7 +69,7 @@ class PartService:
search: Optional[str] = None,
client_id: Optional[int] = None,
fraction: Optional[str] = None,
country_of_origin: Optional[str] = None
country_of_origin: Optional[str] = None,
) -> tuple[List[Part], int]:
"""
Obtener partes con paginación y filtros
@@ -74,11 +79,13 @@ class PartService:
# Aplicar filtros
if search:
query = query.filter(or_(
Part.description_spanish.ilike(f"%{search}%"),
Part.description_english.ilike(f"%{search}%"),
Part.part_number.ilike(f"%{search}%")
))
query = query.filter(
or_(
Part.description_spanish.ilike(f"%{search}%"),
Part.description_english.ilike(f"%{search}%"),
Part.part_number.ilike(f"%{search}%"),
)
)
if client_id is not None:
query = query.filter(Part.client_id == client_id)
@@ -117,15 +124,21 @@ class PartService:
Buscar partes por fracción arancelaria
"""
try:
return db.query(Part).filter(
or_(
Part.fraction.ilike(f"%{fraction}%"),
Part.us_fraction.ilike(f"%{fraction}%")
return (
db.query(Part)
.filter(
or_(
Part.fraction.ilike(f"%{fraction}%"),
Part.us_fraction.ilike(f"%{fraction}%"),
)
)
).all()
.all()
)
except Exception as e:
logger.error(f"Error searching parts by fraction: {e}")
raise HTTPException(status_code=500, detail="Error searching parts by fraction")
raise HTTPException(
status_code=500, detail="Error searching parts by fraction"
)
@staticmethod
def search_parts_by_supplier(db: Session, supplier: str) -> List[Part]:
@@ -136,7 +149,9 @@ class PartService:
return db.query(Part).filter(Part.supplier.ilike(f"%{supplier}%")).all()
except Exception as e:
logger.error(f"Error searching parts by supplier: {e}")
raise HTTPException(status_code=500, detail="Error searching parts by supplier")
raise HTTPException(
status_code=500, detail="Error searching parts by supplier"
)
@staticmethod
def search_parts_by_country(db: Session, country_code: str) -> List[Part]:
@@ -147,10 +162,14 @@ class PartService:
return db.query(Part).filter(Part.country_of_origin == country_code).all()
except Exception as e:
logger.error(f"Error searching parts by country: {e}")
raise HTTPException(status_code=500, detail="Error searching parts by country")
raise HTTPException(
status_code=500, detail="Error searching parts by country"
)
@staticmethod
def update_part(db: Session, client_id: int, part_number: str, part_data: PartUpdateDTO) -> Optional[Part]:
def update_part(
db: Session, client_id: int, part_number: str, part_data: PartUpdateDTO
) -> Optional[Part]:
"""
Actualizar una parte existente
"""
@@ -190,7 +209,9 @@ class PartService:
raise HTTPException(status_code=500, detail="Error deleting part")
@staticmethod
def toggle_part_status(db: Session, client_id: int, part_number: str) -> Optional[Part]:
def toggle_part_status(
db: Session, client_id: int, part_number: str
) -> Optional[Part]:
"""
Cambiar el estado habilitado/deshabilitado de una parte
"""
@@ -219,17 +240,21 @@ class PartService:
total_parts = db.query(Part).count()
# Partes por cliente
parts_by_client = db.query(
Part.client_id,
func.count(Part.part_number).label('count')
).group_by(Part.client_id).all()
parts_by_client = (
db.query(Part.client_id, func.count(Part.part_number).label("count"))
.group_by(Part.client_id)
.all()
)
# Partes por país de origen
parts_by_country = db.query(
Part.country_of_origin,
func.count(Part.part_number).label('count')
).filter(Part.country_of_origin.isnot(None))\
.group_by(Part.country_of_origin).all()
parts_by_country = (
db.query(
Part.country_of_origin, func.count(Part.part_number).label("count")
)
.filter(Part.country_of_origin.isnot(None))
.group_by(Part.country_of_origin)
.all()
)
# Partes habilitadas vs deshabilitadas
enabled_parts = db.query(Part).filter(Part.enabled_disabled == 1).count()
@@ -239,15 +264,23 @@ class PartService:
"total_parts": total_parts,
"enabled_parts": enabled_parts,
"disabled_parts": disabled_parts,
"parts_by_client": [{"client_id": item[0], "count": item[1]} for item in parts_by_client],
"parts_by_country": [{"country": item[0], "count": item[1]} for item in parts_by_country]
"parts_by_client": [
{"client_id": item[0], "count": item[1]} for item in parts_by_client
],
"parts_by_country": [
{"country": item[0], "count": item[1]} for item in parts_by_country
],
}
except Exception as e:
logger.error(f"Error getting parts statistics: {e}")
raise HTTPException(status_code=500, detail="Error retrieving parts statistics")
raise HTTPException(
status_code=500, detail="Error retrieving parts statistics"
)
@staticmethod
def get_part_regulatory_info(db: Session, client_id: int, part_number: str) -> Optional[dict]:
def get_part_regulatory_info(
db: Session, client_id: int, part_number: str
) -> Optional[dict]:
"""
Obtener información regulatoria específica de una parte
"""
@@ -267,9 +300,10 @@ class PartService:
"eccn": db_part.eccn,
"export_code": db_part.export_code,
"exclusion_symbol": db_part.exclusion_symbol,
"country_of_origin": db_part.country_of_origin
"country_of_origin": db_part.country_of_origin,
}
except Exception as e:
logger.error(f"Error getting part regulatory info: {e}")
raise HTTPException(status_code=500, detail="Error retrieving part regulatory information")
raise HTTPException(
status_code=500, detail="Error retrieving part regulatory information"
)

View File

@@ -5,23 +5,34 @@ from datetime import datetime
class PedimentoConfigAdditionalBase(BaseModel):
"""Base schema for Pedimento Config Additional"""
pedimento_id: int = Field(..., description="Pedimento ID")
tenant_id: int = Field(..., description="Tenant ID")
add_po_identifier: Optional[int] = Field(None, description="Add PO identifier")
do_not_exempt_norms_complement_x: Optional[int] = Field(None, description="Do not exempt norms complement X")
manual_pedimento_year: Optional[str] = Field(None, max_length=2, description="Manual pedimento year")
enable_import_invoice_recipient: Optional[int] = Field(None, description="Enable import invoice recipient")
send_502_validation_file_for_consolidated: Optional[int] = Field(None, description="Send 502 validation file for consolidated")
do_not_exempt_norms_complement_x: Optional[int] = Field(
None, description="Do not exempt norms complement X"
)
manual_pedimento_year: Optional[str] = Field(
None, max_length=2, description="Manual pedimento year"
)
enable_import_invoice_recipient: Optional[int] = Field(
None, description="Enable import invoice recipient"
)
send_502_validation_file_for_consolidated: Optional[int] = Field(
None, description="Send 502 validation file for consolidated"
)
add_remove_norms: Optional[int] = Field(None, description="Add/remove norms")
class PedimentoConfigAdditionalCreate(PedimentoConfigAdditionalBase):
"""Schema for creating a new Pedimento Config Additional"""
pass
class PedimentoConfigAdditionalUpdate(BaseModel):
"""Schema for updating a Pedimento Config Additional"""
add_po_identifier: Optional[int] = None
do_not_exempt_norms_complement_x: Optional[int] = None
manual_pedimento_year: Optional[str] = Field(None, max_length=2)
@@ -32,6 +43,7 @@ class PedimentoConfigAdditionalUpdate(BaseModel):
class PedimentoConfigAdditionalResponse(PedimentoConfigAdditionalBase):
"""Schema for Pedimento Config Additional response"""
id: int
created_at: datetime

View File

@@ -5,27 +5,40 @@ from datetime import datetime
class PedimentoConfigCalculationsBase(BaseModel):
"""Base schema for Pedimento Config Calculations"""
pedimento_id: int = Field(..., description="Pedimento ID")
tenant_id: int = Field(..., description="Tenant ID")
dta_type: Optional[str] = Field(None, max_length=1, description="DTA type")
dta_operation: Optional[int] = Field(None, description="DTA operation")
dta_vehicle_count: Optional[int] = Field(None, description="DTA vehicle count")
dta_mixed_rate_8permil: Optional[int] = Field(None, description="DTA mixed rate 8 per mil")
dta_mixed_rate_8permil: Optional[int] = Field(
None, description="DTA mixed rate 8 per mil"
)
pays_vat: Optional[int] = Field(None, description="Pays VAT")
pays_prevalidation: Optional[int] = Field(None, description="Pays prevalidation")
include_sagar_certificate_fee: Optional[int] = Field(None, description="Include SAGAR certificate fee")
fixed_vehicle_dta_fee: Optional[int] = Field(None, description="Fixed vehicle DTA fee")
additional_fixed_fee: Optional[int] = Field(None, description="Additional fixed fee")
additional_fixed_fee_payment_method: Optional[int] = Field(None, description="Additional fixed fee payment method")
include_sagar_certificate_fee: Optional[int] = Field(
None, description="Include SAGAR certificate fee"
)
fixed_vehicle_dta_fee: Optional[int] = Field(
None, description="Fixed vehicle DTA fee"
)
additional_fixed_fee: Optional[int] = Field(
None, description="Additional fixed fee"
)
additional_fixed_fee_payment_method: Optional[int] = Field(
None, description="Additional fixed fee payment method"
)
class PedimentoConfigCalculationsCreate(PedimentoConfigCalculationsBase):
"""Schema for creating a new Pedimento Config Calculations"""
pass
class PedimentoConfigCalculationsUpdate(BaseModel):
"""Schema for updating a Pedimento Config Calculations"""
dta_type: Optional[str] = Field(None, max_length=1)
dta_operation: Optional[int] = None
dta_vehicle_count: Optional[int] = None
@@ -40,6 +53,7 @@ class PedimentoConfigCalculationsUpdate(BaseModel):
class PedimentoConfigCalculationsResponse(PedimentoConfigCalculationsBase):
"""Schema for Pedimento Config Calculations response"""
id: int
created_at: datetime

View File

@@ -6,28 +6,43 @@ from datetime import datetime
class PedimentoConfigParametersBase(BaseModel):
"""Base schema for Pedimento Config Parameters"""
pedimento_id: int = Field(..., description="Pedimento ID")
tenant_id: int = Field(..., description="Tenant ID")
is_embassy: Optional[int] = Field(None, description="Is embassy")
embassy_dta: Optional[Decimal] = Field(None, description="Embassy DTA")
rule_3121_section_ii: Optional[int] = Field(None, description="Rule 3.1.21 Section II")
rule_3121_section_ii: Optional[int] = Field(
None, description="Rule 3.1.21 Section II"
)
use_previous_tariff: Optional[int] = Field(None, description="Use previous tariff")
use_payment_date_fi: Optional[int] = Field(None, description="Use payment date FI")
add_state_supplier_record_505: Optional[int] = Field(None, description="Add state supplier record 505")
customs_value_calculation: Optional[int] = Field(None, description="Customs value calculation")
two_decimals_unit_value: Optional[int] = Field(None, description="Two decimals unit value")
customs_value_per_item: Optional[int] = Field(None, description="Customs value per item")
is_national_supplier: Optional[int] = Field(None, description="Is national supplier")
add_state_supplier_record_505: Optional[int] = Field(
None, description="Add state supplier record 505"
)
customs_value_calculation: Optional[int] = Field(
None, description="Customs value calculation"
)
two_decimals_unit_value: Optional[int] = Field(
None, description="Two decimals unit value"
)
customs_value_per_item: Optional[int] = Field(
None, description="Customs value per item"
)
is_national_supplier: Optional[int] = Field(
None, description="Is national supplier"
)
is_consolidated: Optional[int] = Field(None, description="Is consolidated")
class PedimentoConfigParametersCreate(PedimentoConfigParametersBase):
"""Schema for creating a new Pedimento Config Parameters"""
pass
class PedimentoConfigParametersUpdate(BaseModel):
"""Schema for updating a Pedimento Config Parameters"""
is_embassy: Optional[int] = None
embassy_dta: Optional[Decimal] = None
rule_3121_section_ii: Optional[int] = None
@@ -43,6 +58,7 @@ class PedimentoConfigParametersUpdate(BaseModel):
class PedimentoConfigParametersResponse(PedimentoConfigParametersBase):
"""Schema for Pedimento Config Parameters response"""
id: int
created_at: datetime

View File

@@ -5,6 +5,7 @@ from datetime import datetime
class PedimentoConfigSurchargesBase(BaseModel):
"""Base schema for Pedimento Config Surcharges"""
pedimento_id: int = Field(..., description="Pedimento ID")
tenant_id: int = Field(..., description="Tenant ID")
surcharge_igi: Optional[int] = Field(None, description="Surcharge IGI")
@@ -17,11 +18,13 @@ class PedimentoConfigSurchargesBase(BaseModel):
class PedimentoConfigSurchargesCreate(PedimentoConfigSurchargesBase):
"""Schema for creating a new Pedimento Config Surcharges"""
pass
class PedimentoConfigSurchargesUpdate(BaseModel):
"""Schema for updating a Pedimento Config Surcharges"""
surcharge_igi: Optional[int] = None
surcharge_dta: Optional[int] = None
surcharge_vat: Optional[int] = None
@@ -32,6 +35,7 @@ class PedimentoConfigSurchargesUpdate(BaseModel):
class PedimentoConfigSurchargesResponse(PedimentoConfigSurchargesBase):
"""Schema for Pedimento Config Surcharges response"""
id: int
created_at: datetime

View File

@@ -5,6 +5,7 @@ from datetime import datetime
class PedimentoConfigUpdateRectificationBase(BaseModel):
"""Base schema for Pedimento Config Update Rectification"""
pedimento_id: int = Field(..., description="Pedimento ID")
tenant_id: int = Field(..., description="Tenant ID")
update_vat: Optional[int] = Field(None, description="Update VAT")
@@ -16,11 +17,13 @@ class PedimentoConfigUpdateRectificationBase(BaseModel):
class PedimentoConfigUpdateRectificationCreate(PedimentoConfigUpdateRectificationBase):
"""Schema for creating a new Pedimento Config Update Rectification"""
pass
class PedimentoConfigUpdateRectificationUpdate(BaseModel):
"""Schema for updating a Pedimento Config Update Rectification"""
update_vat: Optional[int] = None
update_advalorem: Optional[int] = None
update_cc: Optional[int] = None
@@ -28,8 +31,11 @@ class PedimentoConfigUpdateRectificationUpdate(BaseModel):
calculate_surcharge: Optional[int] = None
class PedimentoConfigUpdateRectificationResponse(PedimentoConfigUpdateRectificationBase):
class PedimentoConfigUpdateRectificationResponse(
PedimentoConfigUpdateRectificationBase
):
"""Schema for Pedimento Config Update Rectification response"""
id: int
created_at: datetime

View File

@@ -5,6 +5,7 @@ from datetime import datetime
class PedimentoConfigUpdatesBase(BaseModel):
"""Base schema for Pedimento Config Updates"""
pedimento_id: int = Field(..., description="Pedimento ID")
tenant_id: int = Field(..., description="Tenant ID")
update_vat: Optional[int] = Field(None, description="Update VAT")
@@ -15,11 +16,13 @@ class PedimentoConfigUpdatesBase(BaseModel):
class PedimentoConfigUpdatesCreate(PedimentoConfigUpdatesBase):
"""Schema for creating a new Pedimento Config Updates"""
pass
class PedimentoConfigUpdatesUpdate(BaseModel):
"""Schema for updating a Pedimento Config Updates"""
update_vat: Optional[int] = None
update_advalorem: Optional[int] = None
update_cc: Optional[int] = None
@@ -28,6 +31,7 @@ class PedimentoConfigUpdatesUpdate(BaseModel):
class PedimentoConfigUpdatesResponse(PedimentoConfigUpdatesBase):
"""Schema for Pedimento Config Updates response"""
id: int
created_at: datetime

View File

@@ -5,25 +5,33 @@ from datetime import datetime
class PedimentoCustomsOfficesBase(BaseModel):
"""Base schema for Pedimento Customs Offices"""
pedimento_id: int = Field(..., description="Pedimento ID")
tenant_id: int = Field(..., description="Tenant ID")
dispatch_customs: Optional[str] = Field(None, max_length=3, description="Dispatch customs")
entry_exit_customs: Optional[str] = Field(None, max_length=3, description="Entry/exit customs")
dispatch_customs: Optional[str] = Field(
None, max_length=3, description="Dispatch customs"
)
entry_exit_customs: Optional[str] = Field(
None, max_length=3, description="Entry/exit customs"
)
class PedimentoCustomsOfficesCreate(PedimentoCustomsOfficesBase):
"""Schema for creating a new Pedimento Customs Offices"""
pass
class PedimentoCustomsOfficesUpdate(BaseModel):
"""Schema for updating a Pedimento Customs Offices"""
dispatch_customs: Optional[str] = Field(None, max_length=3)
entry_exit_customs: Optional[str] = Field(None, max_length=3)
class PedimentoCustomsOfficesResponse(PedimentoCustomsOfficesBase):
"""Schema for Pedimento Customs Offices response"""
id: int
created_at: datetime

View File

@@ -5,10 +5,13 @@ from datetime import datetime, time
class PedimentoDatesBase(BaseModel):
"""Base schema for Pedimento Dates"""
entry_date: Optional[datetime] = Field(None, description="Entry date")
pedimento_date: Optional[datetime] = Field(None, description="Pedimento date")
payment_date: Optional[datetime] = Field(None, description="Payment date")
rectification_payment_date: Optional[datetime] = Field(None, description="Rectification payment date")
rectification_payment_date: Optional[datetime] = Field(
None, description="Rectification payment date"
)
extraction_date: Optional[datetime] = Field(None, description="Extraction date")
submission_date: Optional[datetime] = Field(None, description="Submission date")
eucan_date: Optional[datetime] = Field(None, description="EUCAN date")
@@ -21,11 +24,13 @@ class PedimentoDatesBase(BaseModel):
class PedimentoDatesCreate(PedimentoDatesBase):
"""Schema for creating a new Pedimento Dates"""
pass
class PedimentoDatesUpdate(BaseModel):
"""Schema for updating a Pedimento Dates"""
entry_date: Optional[datetime] = None
pedimento_date: Optional[datetime] = None
payment_date: Optional[datetime] = None
@@ -42,6 +47,7 @@ class PedimentoDatesUpdate(BaseModel):
class PedimentoDatesResponse(PedimentoDatesBase):
"""Schema for Pedimento Dates response"""
id: int
pedimento_id: int = Field(..., description="Pedimento ID")
tenant_id: int = Field(..., description="Tenant ID")

View File

@@ -6,6 +6,7 @@ from datetime import datetime
class PedimentoDecrementablesBase(BaseModel):
"""Base schema for Pedimento Decrementables"""
pedimento_id: int = Field(..., description="Pedimento ID")
tenant_id: int = Field(..., description="Tenant ID")
freight: Optional[Decimal] = Field(None, description="Freight")
@@ -15,17 +16,23 @@ class PedimentoDecrementablesBase(BaseModel):
others: Optional[Decimal] = Field(None, description="Others")
currency: Optional[str] = Field(None, max_length=3, description="Currency")
currency_factor: Optional[Decimal] = Field(None, description="Currency factor")
not_affect_usd_value: Optional[int] = Field(None, description="Not affect USD value")
not_affect_customs_value: Optional[int] = Field(None, description="Not affect customs value")
not_affect_usd_value: Optional[int] = Field(
None, description="Not affect USD value"
)
not_affect_customs_value: Optional[int] = Field(
None, description="Not affect customs value"
)
class PedimentoDecrementablesCreate(PedimentoDecrementablesBase):
"""Schema for creating a new Pedimento Decrementables"""
pass
class PedimentoDecrementablesUpdate(BaseModel):
"""Schema for updating a Pedimento Decrementables"""
freight: Optional[Decimal] = None
insurance: Optional[Decimal] = None
loading: Optional[Decimal] = None
@@ -39,6 +46,7 @@ class PedimentoDecrementablesUpdate(BaseModel):
class PedimentoDecrementablesResponse(PedimentoDecrementablesBase):
"""Schema for Pedimento Decrementables response"""
id: int
created_at: datetime

View File

@@ -6,6 +6,7 @@ from datetime import datetime
class PedimentoIncrementablesBase(BaseModel):
"""Base schema for Pedimento Incrementables"""
pedimento_id: int = Field(..., description="Pedimento ID")
tenant_id: int = Field(..., description="Tenant ID")
insured_value: Optional[Decimal] = Field(None, description="Insured value")
@@ -16,17 +17,23 @@ class PedimentoIncrementablesBase(BaseModel):
deductibles: Optional[Decimal] = Field(None, description="Deductibles")
currency: Optional[str] = Field(None, max_length=3, description="Currency")
currency_factor: Optional[Decimal] = Field(None, description="Currency factor")
not_affect_usd_value: Optional[int] = Field(None, description="Not affect USD value")
not_affect_customs_value: Optional[int] = Field(None, description="Not affect customs value")
not_affect_usd_value: Optional[int] = Field(
None, description="Not affect USD value"
)
not_affect_customs_value: Optional[int] = Field(
None, description="Not affect customs value"
)
class PedimentoIncrementablesCreate(PedimentoIncrementablesBase):
"""Schema for creating a new Pedimento Incrementables"""
pass
class PedimentoIncrementablesUpdate(BaseModel):
"""Schema for updating a Pedimento Incrementables"""
insured_value: Optional[Decimal] = None
freight: Optional[Decimal] = None
insurance: Optional[Decimal] = None
@@ -41,6 +48,7 @@ class PedimentoIncrementablesUpdate(BaseModel):
class PedimentoIncrementablesResponse(PedimentoIncrementablesBase):
"""Schema for Pedimento Incrementables response"""
id: int
created_at: datetime

View File

@@ -6,20 +6,25 @@ from datetime import datetime
class PedimentoIndexesBase(BaseModel):
"""Base schema for Pedimento Indexes"""
pedimento_id: int = Field(..., description="Pedimento ID")
tenant_id: int = Field(..., description="Tenant ID")
update_factor_type: Optional[int] = Field(None, description="Update factor type")
update_factor: Optional[Decimal] = Field(None, description="Update factor")
manual_update_factor: Optional[int] = Field(None, description="Manual update factor")
manual_update_factor: Optional[int] = Field(
None, description="Manual update factor"
)
class PedimentoIndexesCreate(PedimentoIndexesBase):
"""Schema for creating a new Pedimento Indexes"""
pass
class PedimentoIndexesUpdate(BaseModel):
"""Schema for updating a Pedimento Indexes"""
update_factor_type: Optional[int] = None
update_factor: Optional[Decimal] = None
manual_update_factor: Optional[int] = None
@@ -27,6 +32,7 @@ class PedimentoIndexesUpdate(BaseModel):
class PedimentoIndexesResponse(PedimentoIndexesBase):
"""Schema for Pedimento Indexes response"""
id: int
created_at: datetime

View File

@@ -5,10 +5,15 @@ from datetime import datetime, date as Date, time as Time
class PedimentoPaymentsBase(BaseModel):
"""Base schema for Pedimento Payments"""
pedimento_id: int = Field(..., description="Pedimento ID")
tenant_id: int = Field(..., description="Tenant ID")
acknowledgment: Optional[str] = Field(None, max_length=20, description="Acknowledgment")
operation_number: Optional[str] = Field(None, max_length=14, description="Operation number")
acknowledgment: Optional[str] = Field(
None, max_length=20, description="Acknowledgment"
)
operation_number: Optional[str] = Field(
None, max_length=14, description="Operation number"
)
bank_code: Optional[int] = Field(None, description="Bank code")
cashier: Optional[str] = Field(None, max_length=2, description="Cashier")
date: Optional[Date] = Field(None, description="Date")
@@ -23,11 +28,13 @@ class PedimentoPaymentsBase(BaseModel):
class PedimentoPaymentsCreate(PedimentoPaymentsBase):
"""Schema for creating a new Pedimento Payments"""
pass
class PedimentoPaymentsUpdate(BaseModel):
"""Schema for updating a Pedimento Payments"""
acknowledgment: Optional[str] = Field(None, max_length=20)
operation_number: Optional[str] = Field(None, max_length=14)
bank_code: Optional[int] = None
@@ -44,6 +51,7 @@ class PedimentoPaymentsUpdate(BaseModel):
class PedimentoPaymentsResponse(PedimentoPaymentsBase):
"""Schema for Pedimento Payments response"""
id: int
created_at: datetime

View File

@@ -4,21 +4,32 @@ from typing import Optional
class PedimentoRectificationDestinationBase(BaseModel):
"""Base schema for Pedimento Rectification Destination"""
pedimento_id: int = Field(..., description="Pedimento ID")
tenant_id: int = Field(..., description="Tenant ID")
destination_pedimento_year: Optional[str] = Field(None, max_length=2, description="Destination pedimento year")
destination_customs_office: Optional[str] = Field(None, max_length=3, description="Destination customs office")
destination_license: Optional[str] = Field(None, max_length=4, description="Destination license")
destination_pedimento_number: Optional[str] = Field(None, max_length=7, description="Destination pedimento number")
destination_pedimento_year: Optional[str] = Field(
None, max_length=2, description="Destination pedimento year"
)
destination_customs_office: Optional[str] = Field(
None, max_length=3, description="Destination customs office"
)
destination_license: Optional[str] = Field(
None, max_length=4, description="Destination license"
)
destination_pedimento_number: Optional[str] = Field(
None, max_length=7, description="Destination pedimento number"
)
class PedimentoRectificationDestinationCreate(PedimentoRectificationDestinationBase):
"""Schema for creating a new Pedimento Rectification Destination"""
pass
class PedimentoRectificationDestinationUpdate(BaseModel):
"""Schema for updating a Pedimento Rectification Destination"""
destination_pedimento_year: Optional[str] = Field(None, max_length=2)
destination_customs_office: Optional[str] = Field(None, max_length=3)
destination_license: Optional[str] = Field(None, max_length=4)
@@ -27,6 +38,7 @@ class PedimentoRectificationDestinationUpdate(BaseModel):
class PedimentoRectificationDestinationResponse(PedimentoRectificationDestinationBase):
"""Schema for Pedimento Rectification Destination response"""
id: int
model_config = ConfigDict(from_attributes=True)

View File

@@ -5,30 +5,49 @@ from datetime import datetime
class PedimentoRectificationOriginBase(BaseModel):
"""Base schema for Pedimento Rectification Origin"""
pedimento_id: int = Field(..., description="Pedimento ID")
tenant_id: int = Field(..., description="Tenant ID")
original_pedimento_year: Optional[str] = Field(None, max_length=2, description="Original pedimento year")
original_customs_office: Optional[str] = Field(None, max_length=3, description="Original customs office")
original_license: Optional[str] = Field(None, max_length=4, description="Original license")
original_pedimento_number: Optional[str] = Field(None, max_length=7, description="Original pedimento number")
original_pedimento_code: Optional[str] = Field(None, max_length=2, description="Original pedimento key")
original_payment_date: Optional[datetime] = Field(None, description="Original payment date")
original_pedimento_year: Optional[str] = Field(
None, max_length=2, description="Original pedimento year"
)
original_customs_office: Optional[str] = Field(
None, max_length=3, description="Original customs office"
)
original_license: Optional[str] = Field(
None, max_length=4, description="Original license"
)
original_pedimento_number: Optional[str] = Field(
None, max_length=7, description="Original pedimento number"
)
original_pedimento_code: Optional[str] = Field(
None, max_length=2, description="Original pedimento key"
)
original_payment_date: Optional[datetime] = Field(
None, description="Original payment date"
)
total_cash: Optional[int] = Field(None, description="Total cash")
total_others: Optional[int] = Field(None, description="Total others")
reason: Optional[str] = Field(None, max_length=255, description="Reason")
charge_to_client: Optional[int] = Field(None, description="Charge to client")
use_original_payment_date_for_interest_calc: Optional[int] = Field(None, description="Use original payment date for interest calculation")
use_original_payment_date_for_interest_calc: Optional[int] = Field(
None, description="Use original payment date for interest calculation"
)
manual_calculation: Optional[int] = Field(None, description="Manual calculation")
original_pedimento_norms: Optional[int] = Field(None, description="Original pedimento norms")
original_pedimento_norms: Optional[int] = Field(
None, description="Original pedimento norms"
)
class PedimentoRectificationOriginCreate(PedimentoRectificationOriginBase):
"""Schema for creating a new Pedimento Rectification Origin"""
pass
class PedimentoRectificationOriginUpdate(BaseModel):
"""Schema for updating a Pedimento Rectification Origin"""
original_pedimento_year: Optional[str] = Field(None, max_length=2)
original_customs_office: Optional[str] = Field(None, max_length=3)
original_license: Optional[str] = Field(None, max_length=4)
@@ -46,6 +65,7 @@ class PedimentoRectificationOriginUpdate(BaseModel):
class PedimentoRectificationOriginResponse(PedimentoRectificationOriginBase):
"""Schema for Pedimento Rectification Origin response"""
id: int
model_config = ConfigDict(from_attributes=True)

View File

@@ -5,6 +5,7 @@ from datetime import datetime
class PedimentoTransportMeansBase(BaseModel):
"""Base schema for Pedimento Transport Means"""
pedimento_id: int = Field(..., description="Pedimento ID")
tenant_id: int = Field(..., description="Tenant ID")
destination: Optional[int] = Field(None, description="Destination")
@@ -15,11 +16,13 @@ class PedimentoTransportMeansBase(BaseModel):
class PedimentoTransportMeansCreate(PedimentoTransportMeansBase):
"""Schema for creating a new Pedimento Transport Means"""
pass
class PedimentoTransportMeansUpdate(BaseModel):
"""Schema for updating a Pedimento Transport Means"""
destination: Optional[int] = None
entry_exit: Optional[str] = Field(None, max_length=2)
arrival: Optional[str] = Field(None, max_length=2)
@@ -28,6 +31,7 @@ class PedimentoTransportMeansUpdate(BaseModel):
class PedimentoTransportMeansResponse(PedimentoTransportMeansBase):
"""Schema for Pedimento Transport Means response"""
id: int
created_at: datetime

View File

@@ -5,25 +5,36 @@ from datetime import datetime
class PedimentoValidationBase(BaseModel):
"""Base schema for Pedimento Validation"""
pedimento_id: int = Field(..., description="Pedimento ID")
tenant_id: int = Field(..., description="Tenant ID")
validator: Optional[str] = Field(None, max_length=3, description="Validator")
validation_ack: Optional[str] = Field(None, max_length=8, description="Validation acknowledgment")
validation_ack: Optional[str] = Field(
None, max_length=8, description="Validation acknowledgment"
)
pre_ack: Optional[str] = Field(None, max_length=8, description="Pre-acknowledgment")
line_signature: Optional[str] = Field(None, max_length=50, description="Line signature")
electronic_signature: Optional[str] = Field(None, max_length=999, description="Electronic signature")
certificate_number: Optional[str] = Field(None, max_length=99, description="Certificate number")
line_signature: Optional[str] = Field(
None, max_length=50, description="Line signature"
)
electronic_signature: Optional[str] = Field(
None, max_length=999, description="Electronic signature"
)
certificate_number: Optional[str] = Field(
None, max_length=99, description="Certificate number"
)
validator_id: Optional[int] = Field(None, description="Validator ID")
responsible_id: Optional[int] = Field(None, description="Responsible ID")
class PedimentoValidationCreate(PedimentoValidationBase):
"""Schema for creating a new Pedimento Validation"""
pass
class PedimentoValidationUpdate(BaseModel):
"""Schema for updating a Pedimento Validation"""
validator: Optional[str] = Field(None, max_length=3)
validation_ack: Optional[str] = Field(None, max_length=8)
pre_ack: Optional[str] = Field(None, max_length=8)
@@ -36,6 +47,7 @@ class PedimentoValidationUpdate(BaseModel):
class PedimentoValidationResponse(PedimentoValidationBase):
"""Schema for Pedimento Validation response"""
id: int
created_at: datetime

View File

@@ -4,20 +4,29 @@ from typing import Optional
from decimal import Decimal
from datetime import datetime
class OperationType(IntEnum):
EXPORTACION = 1
IMPORTACION = 2
class PedimentosBase(BaseModel):
"""Base schema for Pedimentos"""
year: Optional[str] = Field(None, max_length=2, description="Year")
customs_office: Optional[str] = Field(None, max_length=2, description="Customs office")
customs_office: Optional[str] = Field(
None, max_length=2, description="Customs office"
)
license: Optional[str] = Field(None, max_length=4, description="License")
pedimento_number: Optional[str] = Field(None, max_length=7, description="Pedimento number")
pedimento_number: Optional[str] = Field(
None, max_length=7, description="Pedimento number"
)
client_id: Optional[int] = Field(None, description="Client ID")
operation_type: Optional[int] = Field(None, description="Operation type")
pedimento_type: Optional[int] = Field(None, description="Pedimento type")
pedimento_code: Optional[str] = Field(None, max_length=2, description="Pedimento key")
pedimento_code: Optional[str] = Field(
None, max_length=2, description="Pedimento key"
)
regime: Optional[str] = Field(None, max_length=3, description="Regime")
status: Optional[str] = Field(None, max_length=30, description="Status")
usd_value: Optional[Decimal] = Field(None, description="USD value")
@@ -28,11 +37,13 @@ class PedimentosBase(BaseModel):
class PedimentosCreate(PedimentosBase):
"""Schema for creating a new Pedimento"""
pass
class PedimentosUpdate(BaseModel):
"""Schema for updating a Pedimento"""
year: Optional[str] = Field(..., max_length=2)
customs_office: Optional[str] = Field(..., max_length=2)
license: Optional[str] = Field(..., max_length=4)
@@ -51,6 +62,7 @@ class PedimentosUpdate(BaseModel):
class PedimentosResponse(PedimentosBase):
"""Schema for Pedimento response"""
id: int
tenant_id: int
created_at: datetime

View File

@@ -1,6 +1,16 @@
from typing import TYPE_CHECKING
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, PrimaryKeyConstraint, SmallInteger, String, UniqueConstraint, func, text
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy import (
DateTime,
ForeignKeyConstraint,
Integer,
PrimaryKeyConstraint,
SmallInteger,
String,
UniqueConstraint,
func,
text,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
from datetime import datetime
from core.database import Base
@@ -9,31 +19,55 @@ if TYPE_CHECKING:
class PedimentoConfigAdditional(Base):
__tablename__ = 'pedimento_config_additional'
__tablename__ = "pedimento_config_additional"
__table_args__ = (
PrimaryKeyConstraint('id', name='pedimento_config_additional_pkey'),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimento_config_additional_tenant'),
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimento_config_additional_company'),
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_config_additional'),
UniqueConstraint('tenant_id', 'company_id', 'pedimento_id', name='pedimento_config_additional_pedimento_id_key'),
{'schema': 'a76'}
PrimaryKeyConstraint("id", name="pedimento_config_additional_pkey"),
ForeignKeyConstraint(
["tenant_id"],
["a76.tenants.id"],
name="fk_pedimento_config_additional_tenant",
),
ForeignKeyConstraint(
["company_id"],
["a76.company.id"],
name="fk_pedimento_config_additional_company",
),
ForeignKeyConstraint(
["pedimento_id"],
["a76.pedimentos.id"],
ondelete="CASCADE",
name="fk_pedimento_config_additional",
),
UniqueConstraint(
"tenant_id",
"company_id",
"pedimento_id",
name="pedimento_config_additional_pedimento_id_key",
),
{"schema": "a76"},
)
id: Mapped [int] = mapped_column(Integer)
tenant_id: Mapped [int] = mapped_column(Integer, nullable=False, index=True)
id: Mapped[int] = mapped_column(Integer)
tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
company_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
pedimento_id: Mapped [int] = mapped_column(Integer, nullable=False)
pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False)
add_po_identifier: Mapped [int] = mapped_column(SmallInteger)
do_not_exempt_norms_complement_x: Mapped [int] = mapped_column(SmallInteger)
manual_pedimento_year: Mapped [str] = mapped_column(String(2))
enable_import_invoice_recipient: Mapped [int] = mapped_column(SmallInteger)
send_502_validation_file_for_consolidated: Mapped [int] = mapped_column(SmallInteger)
add_remove_norms: Mapped [int] = mapped_column(SmallInteger)
add_po_identifier: Mapped[int] = mapped_column(SmallInteger)
do_not_exempt_norms_complement_x: Mapped[int] = mapped_column(SmallInteger)
manual_pedimento_year: Mapped[str] = mapped_column(String(2))
enable_import_invoice_recipient: Mapped[int] = mapped_column(SmallInteger)
send_502_validation_file_for_consolidated: Mapped[int] = mapped_column(SmallInteger)
add_remove_norms: Mapped[int] = mapped_column(SmallInteger)
# Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
created_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now()
)
updated_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now(), onupdate=func.now()
)
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_config_additional')
pedimento: Mapped["Pedimentos"] = relationship(
"Pedimentos", back_populates="pedimento_config_additional"
)

View File

@@ -1,5 +1,15 @@
from typing import TYPE_CHECKING
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, PrimaryKeyConstraint, SmallInteger, String, UniqueConstraint, func, text
from sqlalchemy import (
DateTime,
ForeignKeyConstraint,
Integer,
PrimaryKeyConstraint,
SmallInteger,
String,
UniqueConstraint,
func,
text,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
from datetime import datetime
from core.database import Base
@@ -7,15 +17,26 @@ from core.database import Base
if TYPE_CHECKING:
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
class PedimentoConfigCalculations(Base):
__tablename__ = 'pedimento_config_calculations'
__tablename__ = "pedimento_config_calculations"
__table_args__ = (
PrimaryKeyConstraint('id', name='pedimento_config_calculations_pkey'),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id']),
ForeignKeyConstraint(['company_id'], ['a76.company.id']),
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_config_calculations'),
UniqueConstraint('tenant_id', 'company_id', 'pedimento_id', name='pedimento_config_calculations_pedimento_id_key'),
{'schema': 'a76'}
PrimaryKeyConstraint("id", name="pedimento_config_calculations_pkey"),
ForeignKeyConstraint(["tenant_id"], ["a76.tenants.id"]),
ForeignKeyConstraint(["company_id"], ["a76.company.id"]),
ForeignKeyConstraint(
["pedimento_id"],
["a76.pedimentos.id"],
ondelete="CASCADE",
name="fk_pedimento_config_calculations",
),
UniqueConstraint(
"tenant_id",
"company_id",
"pedimento_id",
name="pedimento_config_calculations_pedimento_id_key",
),
{"schema": "a76"},
)
id: Mapped[int] = mapped_column(Integer)
@@ -35,8 +56,14 @@ class PedimentoConfigCalculations(Base):
additional_fixed_fee_payment_method: Mapped[int] = mapped_column(SmallInteger)
# Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
created_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now()
)
updated_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now(), onupdate=func.now()
)
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_config_calculations')
pedimento: Mapped["Pedimentos"] = relationship(
"Pedimentos", back_populates="pedimento_config_calculations"
)

View File

@@ -1,6 +1,16 @@
from decimal import Decimal
from typing import TYPE_CHECKING
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, Numeric, PrimaryKeyConstraint, SmallInteger, UniqueConstraint, func, text
from sqlalchemy import (
DateTime,
ForeignKeyConstraint,
Integer,
Numeric,
PrimaryKeyConstraint,
SmallInteger,
UniqueConstraint,
func,
text,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped
from datetime import datetime
@@ -11,14 +21,32 @@ if TYPE_CHECKING:
class PedimentoConfigParameters(Base):
__tablename__ = 'pedimento_config_parameters'
__tablename__ = "pedimento_config_parameters"
__table_args__ = (
PrimaryKeyConstraint('id', name='pedimento_config_parameters_pkey'),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimento_config_parameters_tenant'),
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimento_config_parameters_company'),
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_config_parameters'),
UniqueConstraint('tenant_id', 'company_id', 'pedimento_id', name='pedimento_config_parameters_pedimento_id_key'),
{'schema': 'a76'}
PrimaryKeyConstraint("id", name="pedimento_config_parameters_pkey"),
ForeignKeyConstraint(
["tenant_id"],
["a76.tenants.id"],
name="fk_pedimento_config_parameters_tenant",
),
ForeignKeyConstraint(
["company_id"],
["a76.company.id"],
name="fk_pedimento_config_parameters_company",
),
ForeignKeyConstraint(
["pedimento_id"],
["a76.pedimentos.id"],
ondelete="CASCADE",
name="fk_pedimento_config_parameters",
),
UniqueConstraint(
"tenant_id",
"company_id",
"pedimento_id",
name="pedimento_config_parameters_pedimento_id_key",
),
{"schema": "a76"},
)
id: Mapped[int] = mapped_column(Integer)
@@ -39,8 +67,14 @@ class PedimentoConfigParameters(Base):
is_consolidated: Mapped[int] = mapped_column(SmallInteger)
# Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
created_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now()
)
updated_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now(), onupdate=func.now()
)
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_config_parameters')
pedimento: Mapped["Pedimentos"] = relationship(
"Pedimentos", back_populates="pedimento_config_parameters"
)

View File

@@ -1,5 +1,14 @@
from typing import TYPE_CHECKING
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, PrimaryKeyConstraint, SmallInteger, UniqueConstraint, func, text
from sqlalchemy import (
DateTime,
ForeignKeyConstraint,
Integer,
PrimaryKeyConstraint,
SmallInteger,
UniqueConstraint,
func,
text,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped
from datetime import datetime
@@ -8,15 +17,34 @@ from core.database import Base
if TYPE_CHECKING:
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
class PedimentoConfigSurcharges(Base):
__tablename__ = 'pedimento_config_surcharges'
__tablename__ = "pedimento_config_surcharges"
__table_args__ = (
PrimaryKeyConstraint('id', name='pedimento_config_surcharges_pkey'),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimento_config_surcharges_tenant'),
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimento_config_surcharges_company'),
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_config_surcharges'),
UniqueConstraint('tenant_id', 'company_id', 'pedimento_id', name='pedimento_config_surcharges_pedimento_id_key'),
{'schema': 'a76'}
PrimaryKeyConstraint("id", name="pedimento_config_surcharges_pkey"),
ForeignKeyConstraint(
["tenant_id"],
["a76.tenants.id"],
name="fk_pedimento_config_surcharges_tenant",
),
ForeignKeyConstraint(
["company_id"],
["a76.company.id"],
name="fk_pedimento_config_surcharges_company",
),
ForeignKeyConstraint(
["pedimento_id"],
["a76.pedimentos.id"],
ondelete="CASCADE",
name="fk_pedimento_config_surcharges",
),
UniqueConstraint(
"tenant_id",
"company_id",
"pedimento_id",
name="pedimento_config_surcharges_pedimento_id_key",
),
{"schema": "a76"},
)
id: Mapped[int] = mapped_column(Integer)
@@ -32,8 +60,14 @@ class PedimentoConfigSurcharges(Base):
surcharge_cc: Mapped[int] = mapped_column(SmallInteger)
# Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
created_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now()
)
updated_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now(), onupdate=func.now()
)
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_config_surcharges')
pedimento: Mapped["Pedimentos"] = relationship(
"Pedimentos", back_populates="pedimento_config_surcharges"
)

View File

@@ -1,5 +1,14 @@
from typing import TYPE_CHECKING
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, PrimaryKeyConstraint, SmallInteger, UniqueConstraint, func, text
from sqlalchemy import (
DateTime,
ForeignKeyConstraint,
Integer,
PrimaryKeyConstraint,
SmallInteger,
UniqueConstraint,
func,
text,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped
from datetime import datetime
@@ -8,15 +17,34 @@ from core.database import Base
if TYPE_CHECKING:
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
class PedimentoConfigUpdateRectification(Base):
__tablename__ = 'pedimento_config_update_rectification'
__tablename__ = "pedimento_config_update_rectification"
__table_args__ = (
PrimaryKeyConstraint('id', name='pedimento_config_update_rectification_pkey'),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimento_config_update_rectification_tenant'),
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimento_config_update_rectification_company'),
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_config_update_rectification'),
UniqueConstraint('tenant_id', 'company_id', 'pedimento_id', name='pedimento_config_update_rectification_pedimento_id_key'),
{'schema': 'a76'}
PrimaryKeyConstraint("id", name="pedimento_config_update_rectification_pkey"),
ForeignKeyConstraint(
["tenant_id"],
["a76.tenants.id"],
name="fk_pedimento_config_update_rectification_tenant",
),
ForeignKeyConstraint(
["company_id"],
["a76.company.id"],
name="fk_pedimento_config_update_rectification_company",
),
ForeignKeyConstraint(
["pedimento_id"],
["a76.pedimentos.id"],
ondelete="CASCADE",
name="fk_pedimento_config_update_rectification",
),
UniqueConstraint(
"tenant_id",
"company_id",
"pedimento_id",
name="pedimento_config_update_rectification_pedimento_id_key",
),
{"schema": "a76"},
)
id: Mapped[int] = mapped_column(Integer)
@@ -31,8 +59,14 @@ class PedimentoConfigUpdateRectification(Base):
calculate_surcharge: Mapped[int] = mapped_column(SmallInteger)
# Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
created_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now()
)
updated_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now(), onupdate=func.now()
)
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_config_update_rectification')
pedimento: Mapped["Pedimentos"] = relationship(
"Pedimentos", back_populates="pedimento_config_update_rectification"
)

View File

@@ -1,5 +1,14 @@
from typing import TYPE_CHECKING
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, PrimaryKeyConstraint, SmallInteger, UniqueConstraint, func, text
from sqlalchemy import (
DateTime,
ForeignKeyConstraint,
Integer,
PrimaryKeyConstraint,
SmallInteger,
UniqueConstraint,
func,
text,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped
from datetime import datetime
@@ -8,15 +17,32 @@ from core.database import Base
if TYPE_CHECKING:
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
class PedimentoConfigUpdates(Base):
__tablename__ = 'pedimento_config_updates'
__tablename__ = "pedimento_config_updates"
__table_args__ = (
PrimaryKeyConstraint('id', name='pedimento_config_updates_pkey'),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimento_config_updates_tenant'),
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimento_config_updates_company'),
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_config_updates'),
UniqueConstraint('tenant_id', 'company_id', 'pedimento_id', name='pedimento_config_updates_pedimento_id_key'),
{'schema': 'a76'}
PrimaryKeyConstraint("id", name="pedimento_config_updates_pkey"),
ForeignKeyConstraint(
["tenant_id"], ["a76.tenants.id"], name="fk_pedimento_config_updates_tenant"
),
ForeignKeyConstraint(
["company_id"],
["a76.company.id"],
name="fk_pedimento_config_updates_company",
),
ForeignKeyConstraint(
["pedimento_id"],
["a76.pedimentos.id"],
ondelete="CASCADE",
name="fk_pedimento_config_updates",
),
UniqueConstraint(
"tenant_id",
"company_id",
"pedimento_id",
name="pedimento_config_updates_pedimento_id_key",
),
{"schema": "a76"},
)
id: Mapped[int] = mapped_column(Integer)
@@ -30,8 +56,14 @@ class PedimentoConfigUpdates(Base):
update_ieps: Mapped[int] = mapped_column(SmallInteger)
# Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
created_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now()
)
updated_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now(), onupdate=func.now()
)
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_config_updates')
pedimento: Mapped["Pedimentos"] = relationship(
"Pedimentos", back_populates="pedimento_config_updates"
)

View File

@@ -1,5 +1,14 @@
from typing import TYPE_CHECKING
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, PrimaryKeyConstraint, String, UniqueConstraint, func, text
from sqlalchemy import (
DateTime,
ForeignKeyConstraint,
Integer,
PrimaryKeyConstraint,
String,
UniqueConstraint,
func,
text,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped
from datetime import datetime
@@ -8,15 +17,34 @@ from core.database import Base
if TYPE_CHECKING:
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
class PedimentoCustomsOffices(Base):
__tablename__ = 'pedimento_customs_offices'
__tablename__ = "pedimento_customs_offices"
__table_args__ = (
PrimaryKeyConstraint('id', name='pedimento_customs_offices_pkey'),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimento_customs_offices_tenant'),
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimento_customs_offices_company'),
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_customs_offices'),
UniqueConstraint('tenant_id', 'company_id', 'pedimento_id', name='pedimento_customs_offices_pedimento_id_key'),
{'schema': 'a76'}
PrimaryKeyConstraint("id", name="pedimento_customs_offices_pkey"),
ForeignKeyConstraint(
["tenant_id"],
["a76.tenants.id"],
name="fk_pedimento_customs_offices_tenant",
),
ForeignKeyConstraint(
["company_id"],
["a76.company.id"],
name="fk_pedimento_customs_offices_company",
),
ForeignKeyConstraint(
["pedimento_id"],
["a76.pedimentos.id"],
ondelete="CASCADE",
name="fk_pedimento_customs_offices",
),
UniqueConstraint(
"tenant_id",
"company_id",
"pedimento_id",
name="pedimento_customs_offices_pedimento_id_key",
),
{"schema": "a76"},
)
id: Mapped[int] = mapped_column(Integer)
@@ -28,8 +56,14 @@ class PedimentoCustomsOffices(Base):
entry_exit_customs: Mapped[str] = mapped_column(String(3))
# Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
created_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now()
)
updated_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now(), onupdate=func.now()
)
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_customs_offices')
pedimento: Mapped["Pedimentos"] = relationship(
"Pedimentos", back_populates="pedimento_customs_offices"
)

View File

@@ -1,5 +1,15 @@
from typing import TYPE_CHECKING
from sqlalchemy import DateTime, ForeignKeyConstraint, Index, Integer, PrimaryKeyConstraint, Time, UniqueConstraint, func, text
from sqlalchemy import (
DateTime,
ForeignKeyConstraint,
Index,
Integer,
PrimaryKeyConstraint,
Time,
UniqueConstraint,
func,
text,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped
from datetime import datetime, time as datetime_time
@@ -8,16 +18,31 @@ from core.database import Base
if TYPE_CHECKING:
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
class PedimentoDates(Base):
__tablename__ = 'pedimento_dates'
__tablename__ = "pedimento_dates"
__table_args__ = (
PrimaryKeyConstraint('id', name='pedimento_dates_pkey'),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimento_dates_tenant'),
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimento_dates_company'),
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_dates'),
UniqueConstraint('tenant_id', 'company_id', 'pedimento_id', name='pedimento_dates_pedimento_id_key'),
Index('idx_pedimento_dates_pedimento_id', 'pedimento_id'),
{'schema': 'a76'}
PrimaryKeyConstraint("id", name="pedimento_dates_pkey"),
ForeignKeyConstraint(
["tenant_id"], ["a76.tenants.id"], name="fk_pedimento_dates_tenant"
),
ForeignKeyConstraint(
["company_id"], ["a76.company.id"], name="fk_pedimento_dates_company"
),
ForeignKeyConstraint(
["pedimento_id"],
["a76.pedimentos.id"],
ondelete="CASCADE",
name="fk_pedimento_dates",
),
UniqueConstraint(
"tenant_id",
"company_id",
"pedimento_id",
name="pedimento_dates_pedimento_id_key",
),
Index("idx_pedimento_dates_pedimento_id", "pedimento_id"),
{"schema": "a76"},
)
id: Mapped[int] = mapped_column(Integer)
@@ -39,8 +64,14 @@ class PedimentoDates(Base):
capture_time: Mapped[datetime_time] = mapped_column(Time)
# Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
created_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now()
)
updated_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now(), onupdate=func.now()
)
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_dates')
pedimento: Mapped["Pedimentos"] = relationship(
"Pedimentos", back_populates="pedimento_dates"
)

View File

@@ -1,6 +1,17 @@
from decimal import Decimal
from typing import TYPE_CHECKING
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, Numeric, PrimaryKeyConstraint, SmallInteger, String, UniqueConstraint, func, text
from sqlalchemy import (
DateTime,
ForeignKeyConstraint,
Integer,
Numeric,
PrimaryKeyConstraint,
SmallInteger,
String,
UniqueConstraint,
func,
text,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped
from datetime import datetime
@@ -9,15 +20,32 @@ from core.database import Base
if TYPE_CHECKING:
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
class PedimentoDecrementables(Base):
__tablename__ = 'pedimento_decrementables'
__tablename__ = "pedimento_decrementables"
__table_args__ = (
PrimaryKeyConstraint('id', name='pedimento_decrementables_pkey'),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimento_decrementables_tenant'),
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimento_decrementables_company'),
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_decrementables'),
UniqueConstraint('tenant_id', 'company_id', 'pedimento_id', name='pedimento_decrementables_pedimento_id_key'),
{'schema': 'a76'}
PrimaryKeyConstraint("id", name="pedimento_decrementables_pkey"),
ForeignKeyConstraint(
["tenant_id"], ["a76.tenants.id"], name="fk_pedimento_decrementables_tenant"
),
ForeignKeyConstraint(
["company_id"],
["a76.company.id"],
name="fk_pedimento_decrementables_company",
),
ForeignKeyConstraint(
["pedimento_id"],
["a76.pedimentos.id"],
ondelete="CASCADE",
name="fk_pedimento_decrementables",
),
UniqueConstraint(
"tenant_id",
"company_id",
"pedimento_id",
name="pedimento_decrementables_pedimento_id_key",
),
{"schema": "a76"},
)
id: Mapped[int] = mapped_column(Integer)
@@ -36,8 +64,14 @@ class PedimentoDecrementables(Base):
not_affect_customs_value: Mapped[int] = mapped_column(SmallInteger)
# Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
created_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now()
)
updated_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now(), onupdate=func.now()
)
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_decrementables')
pedimento: Mapped["Pedimentos"] = relationship(
"Pedimentos", back_populates="pedimento_decrementables"
)

View File

@@ -1,6 +1,17 @@
from decimal import Decimal
from typing import TYPE_CHECKING
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, Numeric, PrimaryKeyConstraint, SmallInteger, String, UniqueConstraint, func, text
from sqlalchemy import (
DateTime,
ForeignKeyConstraint,
Integer,
Numeric,
PrimaryKeyConstraint,
SmallInteger,
String,
UniqueConstraint,
func,
text,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped
from datetime import datetime
@@ -9,15 +20,32 @@ from core.database import Base
if TYPE_CHECKING:
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
class PedimentoIncrementables(Base):
__tablename__ = 'pedimento_incrementables'
__tablename__ = "pedimento_incrementables"
__table_args__ = (
PrimaryKeyConstraint('id', name='pedimento_incrementables_pkey'),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimento_incrementables_tenant'),
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimento_incrementables_company'),
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_incrementables'),
UniqueConstraint('tenant_id', 'company_id', 'pedimento_id', name='pedimento_incrementables_pedimento_id_key'),
{'schema': 'a76'}
PrimaryKeyConstraint("id", name="pedimento_incrementables_pkey"),
ForeignKeyConstraint(
["tenant_id"], ["a76.tenants.id"], name="fk_pedimento_incrementables_tenant"
),
ForeignKeyConstraint(
["company_id"],
["a76.company.id"],
name="fk_pedimento_incrementables_company",
),
ForeignKeyConstraint(
["pedimento_id"],
["a76.pedimentos.id"],
ondelete="CASCADE",
name="fk_pedimento_incrementables",
),
UniqueConstraint(
"tenant_id",
"company_id",
"pedimento_id",
name="pedimento_incrementables_pedimento_id_key",
),
{"schema": "a76"},
)
id: Mapped[int] = mapped_column(Integer)
@@ -37,8 +65,14 @@ class PedimentoIncrementables(Base):
not_affect_customs_value: Mapped[int] = mapped_column(SmallInteger)
# Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
created_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now()
)
updated_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now(), onupdate=func.now()
)
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_incrementables')
pedimento: Mapped["Pedimentos"] = relationship(
"Pedimentos", back_populates="pedimento_incrementables"
)

View File

@@ -1,6 +1,16 @@
from decimal import Decimal
from typing import TYPE_CHECKING
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, Numeric, PrimaryKeyConstraint, SmallInteger, UniqueConstraint, func, text
from sqlalchemy import (
DateTime,
ForeignKeyConstraint,
Integer,
Numeric,
PrimaryKeyConstraint,
SmallInteger,
UniqueConstraint,
func,
text,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped
from datetime import datetime
@@ -9,15 +19,30 @@ from core.database import Base
if TYPE_CHECKING:
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
class PedimentoIndexes(Base):
__tablename__ = 'pedimento_indexes'
__tablename__ = "pedimento_indexes"
__table_args__ = (
PrimaryKeyConstraint('id', name='pedimento_indexes_pkey'),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimento_indexes_tenant'),
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimento_indexes_company'),
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_indexes'),
UniqueConstraint('tenant_id', 'company_id', 'pedimento_id', name='pedimento_indexes_pedimento_id_key'),
{'schema': 'a76'}
PrimaryKeyConstraint("id", name="pedimento_indexes_pkey"),
ForeignKeyConstraint(
["tenant_id"], ["a76.tenants.id"], name="fk_pedimento_indexes_tenant"
),
ForeignKeyConstraint(
["company_id"], ["a76.company.id"], name="fk_pedimento_indexes_company"
),
ForeignKeyConstraint(
["pedimento_id"],
["a76.pedimentos.id"],
ondelete="CASCADE",
name="fk_pedimento_indexes",
),
UniqueConstraint(
"tenant_id",
"company_id",
"pedimento_id",
name="pedimento_indexes_pedimento_id_key",
),
{"schema": "a76"},
)
id: Mapped[int] = mapped_column(Integer)
@@ -30,8 +55,14 @@ class PedimentoIndexes(Base):
manual_update_factor: Mapped[int] = mapped_column(SmallInteger)
# Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
created_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now()
)
updated_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now(), onupdate=func.now()
)
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_indexes')
pedimento: Mapped["Pedimentos"] = relationship(
"Pedimentos", back_populates="pedimento_indexes"
)

View File

@@ -1,5 +1,18 @@
from typing import TYPE_CHECKING
from sqlalchemy import Date, DateTime, ForeignKeyConstraint, Index, Integer, PrimaryKeyConstraint, SmallInteger, String, Time, UniqueConstraint, func, text
from sqlalchemy import (
Date,
DateTime,
ForeignKeyConstraint,
Index,
Integer,
PrimaryKeyConstraint,
SmallInteger,
String,
Time,
UniqueConstraint,
func,
text,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped
from datetime import datetime, time as Time2, date as Date2
@@ -8,16 +21,31 @@ from core.database import Base
if TYPE_CHECKING:
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
class PedimentoPayments(Base):
__tablename__ = 'pedimento_payments'
__tablename__ = "pedimento_payments"
__table_args__ = (
PrimaryKeyConstraint('id', name='pedimento_payments_pkey'),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimento_payments_tenant'),
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimento_payments_company'),
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_payments'),
UniqueConstraint('tenant_id', 'company_id', 'pedimento_id', name='pedimento_payments_pedimento_id_key'),
Index('idx_pedimento_payments_pedimento_id', 'pedimento_id'),
{'schema': 'a76'}
PrimaryKeyConstraint("id", name="pedimento_payments_pkey"),
ForeignKeyConstraint(
["tenant_id"], ["a76.tenants.id"], name="fk_pedimento_payments_tenant"
),
ForeignKeyConstraint(
["company_id"], ["a76.company.id"], name="fk_pedimento_payments_company"
),
ForeignKeyConstraint(
["pedimento_id"],
["a76.pedimentos.id"],
ondelete="CASCADE",
name="fk_pedimento_payments",
),
UniqueConstraint(
"tenant_id",
"company_id",
"pedimento_id",
name="pedimento_payments_pedimento_id_key",
),
Index("idx_pedimento_payments_pedimento_id", "pedimento_id"),
{"schema": "a76"},
)
id: Mapped[int] = mapped_column(Integer)
@@ -39,8 +67,14 @@ class PedimentoPayments(Base):
pece_code: Mapped[str] = mapped_column(String(5))
# Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
created_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now()
)
updated_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now(), onupdate=func.now()
)
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_payments')
pedimento: Mapped["Pedimentos"] = relationship(
"Pedimentos", back_populates="pedimento_payments"
)

View File

@@ -1,6 +1,14 @@
from datetime import datetime
from typing import TYPE_CHECKING
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, PrimaryKeyConstraint, String, UniqueConstraint, func
from sqlalchemy import (
DateTime,
ForeignKeyConstraint,
Integer,
PrimaryKeyConstraint,
String,
UniqueConstraint,
func,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped
from core.database import Base
@@ -8,15 +16,34 @@ from core.database import Base
if TYPE_CHECKING:
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
class PedimentoRectificationDestination(Base):
__tablename__ = 'pedimento_rectification_destination'
__tablename__ = "pedimento_rectification_destination"
__table_args__ = (
PrimaryKeyConstraint('id', name='pedimento_rectification_destination_pkey'),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimento_rectification_destination_tenant'),
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimento_rectification_destination_company'),
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_rectification_destination'),
UniqueConstraint('tenant_id', 'company_id', 'pedimento_id', name='pedimento_rectification_destination_pedimento_id_key'),
{'schema': 'a76'}
PrimaryKeyConstraint("id", name="pedimento_rectification_destination_pkey"),
ForeignKeyConstraint(
["tenant_id"],
["a76.tenants.id"],
name="fk_pedimento_rectification_destination_tenant",
),
ForeignKeyConstraint(
["company_id"],
["a76.company.id"],
name="fk_pedimento_rectification_destination_company",
),
ForeignKeyConstraint(
["pedimento_id"],
["a76.pedimentos.id"],
ondelete="CASCADE",
name="fk_pedimento_rectification_destination",
),
UniqueConstraint(
"tenant_id",
"company_id",
"pedimento_id",
name="pedimento_rectification_destination_pedimento_id_key",
),
{"schema": "a76"},
)
id: Mapped[int] = mapped_column(Integer)
@@ -30,8 +57,14 @@ class PedimentoRectificationDestination(Base):
destination_pedimento_number: Mapped[str] = mapped_column(String(7))
# Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
created_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now()
)
updated_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now(), onupdate=func.now()
)
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_rectification_destination')
pedimento: Mapped["Pedimentos"] = relationship(
"Pedimentos", back_populates="pedimento_rectification_destination"
)

View File

@@ -1,6 +1,15 @@
from datetime import datetime
from typing import TYPE_CHECKING
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, PrimaryKeyConstraint, SmallInteger, String, UniqueConstraint, func
from sqlalchemy import (
DateTime,
ForeignKeyConstraint,
Integer,
PrimaryKeyConstraint,
SmallInteger,
String,
UniqueConstraint,
func,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped
from core.database import Base
@@ -8,15 +17,34 @@ from core.database import Base
if TYPE_CHECKING:
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
class PedimentoRectificationOrigin(Base):
__tablename__ = 'pedimento_rectification_origin'
__tablename__ = "pedimento_rectification_origin"
__table_args__ = (
PrimaryKeyConstraint('id', name='pedimento_rectification_origin_pkey'),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimento_rectification_origin_tenant'),
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimento_rectification_origin_company'),
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_rectification_origin'),
UniqueConstraint('tenant_id', 'company_id', 'pedimento_id', name='pedimento_rectification_origin_pedimento_id_key'),
{'schema': 'a76'}
PrimaryKeyConstraint("id", name="pedimento_rectification_origin_pkey"),
ForeignKeyConstraint(
["tenant_id"],
["a76.tenants.id"],
name="fk_pedimento_rectification_origin_tenant",
),
ForeignKeyConstraint(
["company_id"],
["a76.company.id"],
name="fk_pedimento_rectification_origin_company",
),
ForeignKeyConstraint(
["pedimento_id"],
["a76.pedimentos.id"],
ondelete="CASCADE",
name="fk_pedimento_rectification_origin",
),
UniqueConstraint(
"tenant_id",
"company_id",
"pedimento_id",
name="pedimento_rectification_origin_pedimento_id_key",
),
{"schema": "a76"},
)
id: Mapped[int] = mapped_column(Integer)
@@ -34,13 +62,21 @@ class PedimentoRectificationOrigin(Base):
total_others: Mapped[int] = mapped_column(Integer)
reason: Mapped[str] = mapped_column(String(255))
charge_to_client: Mapped[int] = mapped_column(SmallInteger)
use_original_payment_date_for_interest_calc: Mapped[int] = mapped_column(SmallInteger)
use_original_payment_date_for_interest_calc: Mapped[int] = mapped_column(
SmallInteger
)
manual_calculation: Mapped[int] = mapped_column(SmallInteger)
original_pedimento_norms: Mapped[int] = mapped_column(SmallInteger)
# Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
created_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now()
)
updated_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now(), onupdate=func.now()
)
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_rectification_origin')
pedimento: Mapped["Pedimentos"] = relationship(
"Pedimentos", back_populates="pedimento_rectification_origin"
)

View File

@@ -1,6 +1,15 @@
from datetime import datetime
from typing import TYPE_CHECKING
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, PrimaryKeyConstraint, SmallInteger, String, UniqueConstraint, text
from sqlalchemy import (
DateTime,
ForeignKeyConstraint,
Integer,
PrimaryKeyConstraint,
SmallInteger,
String,
UniqueConstraint,
text,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped
from core.database import Base
@@ -8,15 +17,34 @@ from core.database import Base
if TYPE_CHECKING:
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
class PedimentoTransportMeans(Base):
__tablename__ = 'pedimento_transport_means'
__tablename__ = "pedimento_transport_means"
__table_args__ = (
PrimaryKeyConstraint('id', name='pedimento_transport_means_pkey'),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimento_transport_means_tenant'),
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimento_transport_means_company'),
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_transport_means'),
UniqueConstraint('tenant_id', 'company_id', 'pedimento_id', name='pedimento_transport_means_pedimento_id_key'),
{'schema': 'a76'}
PrimaryKeyConstraint("id", name="pedimento_transport_means_pkey"),
ForeignKeyConstraint(
["tenant_id"],
["a76.tenants.id"],
name="fk_pedimento_transport_means_tenant",
),
ForeignKeyConstraint(
["company_id"],
["a76.company.id"],
name="fk_pedimento_transport_means_company",
),
ForeignKeyConstraint(
["pedimento_id"],
["a76.pedimentos.id"],
ondelete="CASCADE",
name="fk_pedimento_transport_means",
),
UniqueConstraint(
"tenant_id",
"company_id",
"pedimento_id",
name="pedimento_transport_means_pedimento_id_key",
),
{"schema": "a76"},
)
id: Mapped[int] = mapped_column(Integer)
@@ -28,6 +56,10 @@ class PedimentoTransportMeans(Base):
entry_exit: Mapped[str] = mapped_column(String(2))
arrival: Mapped[str] = mapped_column(String(2))
departure: Mapped[str] = mapped_column(String(2))
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=text('CURRENT_TIMESTAMP'))
created_at: Mapped[datetime] = mapped_column(
DateTime, server_default=text("CURRENT_TIMESTAMP")
)
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_transport_means')
pedimento: Mapped["Pedimentos"] = relationship(
"Pedimentos", back_populates="pedimento_transport_means"
)

View File

@@ -1,5 +1,14 @@
from typing import TYPE_CHECKING
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, PrimaryKeyConstraint, String, UniqueConstraint, func, text
from sqlalchemy import (
DateTime,
ForeignKeyConstraint,
Integer,
PrimaryKeyConstraint,
String,
UniqueConstraint,
func,
text,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped
from datetime import datetime
@@ -8,14 +17,25 @@ from core.database import Base
if TYPE_CHECKING:
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
class PedimentoValidation(Base):
__tablename__ = 'pedimento_validation' #PedimentoValidacion
__tablename__ = "pedimento_validation" # PedimentoValidacion
__table_args__ = (
PrimaryKeyConstraint('id', name='pedimento_validation_pkey'),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id']),
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_validation'),
UniqueConstraint('tenant_id', 'company_id', 'pedimento_id', name='pedimento_validation_pedimento_id_key'),
{'schema': 'a76'}
PrimaryKeyConstraint("id", name="pedimento_validation_pkey"),
ForeignKeyConstraint(["tenant_id"], ["a76.tenants.id"]),
ForeignKeyConstraint(
["pedimento_id"],
["a76.pedimentos.id"],
ondelete="CASCADE",
name="fk_pedimento_validation",
),
UniqueConstraint(
"tenant_id",
"company_id",
"pedimento_id",
name="pedimento_validation_pedimento_id_key",
),
{"schema": "a76"},
)
id: Mapped[int] = mapped_column(Integer)
@@ -23,20 +43,24 @@ class PedimentoValidation(Base):
company_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False)
validator: Mapped[str] = mapped_column(String(3)) #validador
validation_ack: Mapped[str] = mapped_column(String(8)) #acuse_validacion
pre_ack: Mapped[str] = mapped_column(String(8)) #acuse_previo
line_signature: Mapped[str] = mapped_column(String(50)) #firma_linea_captura
electronic_signature: Mapped[str] = mapped_column(String(999)) #firma_electronica
certificate_number: Mapped[str] = mapped_column(String(99)) #numero_certificado
validator: Mapped[str] = mapped_column(String(3)) # validador
validation_ack: Mapped[str] = mapped_column(String(8)) # acuse_validacion
pre_ack: Mapped[str] = mapped_column(String(8)) # acuse_previo
line_signature: Mapped[str] = mapped_column(String(50)) # firma_linea_captura
electronic_signature: Mapped[str] = mapped_column(String(999)) # firma_electronica
certificate_number: Mapped[str] = mapped_column(String(99)) # numero_certificado
validator_id: Mapped[int] = mapped_column(Integer)
responsible_id: Mapped[int] = mapped_column(Integer)
# Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
created_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now()
)
updated_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now(), onupdate=func.now()
)
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_validation')
pedimento: Mapped["Pedimentos"] = relationship(
"Pedimentos", back_populates="pedimento_validation"
)

View File

@@ -1,6 +1,17 @@
from decimal import Decimal
from typing import TYPE_CHECKING, Optional
from sqlalchemy import DateTime, ForeignKeyConstraint, Index, Integer, Numeric, PrimaryKeyConstraint, String, UniqueConstraint, func, text
from sqlalchemy import (
DateTime,
ForeignKeyConstraint,
Index,
Integer,
Numeric,
PrimaryKeyConstraint,
String,
UniqueConstraint,
func,
text,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped
from datetime import datetime
@@ -8,37 +19,86 @@ from enum import IntEnum
from core.database import Base
if TYPE_CHECKING:
from api.v1.modules.a76.pedmientos.models.pedimento_config_additional import PedimentoConfigAdditional
from api.v1.modules.a76.pedmientos.models.pedimento_config_calculations import PedimentoConfigCalculations
from api.v1.modules.a76.pedmientos.models.pedimento_config_parameters import PedimentoConfigParameters
from api.v1.modules.a76.pedmientos.models.pedimento_config_surcharges import PedimentoConfigSurcharges
from api.v1.modules.a76.pedmientos.models.pedimento_config_update_rectification import PedimentoConfigUpdateRectification
from api.v1.modules.a76.pedmientos.models.pedimento_config_updates import PedimentoConfigUpdates
from api.v1.modules.a76.pedmientos.models.pedimento_customs_offices import PedimentoCustomsOffices
from api.v1.modules.a76.pedmientos.models.pedimento_config_additional import (
PedimentoConfigAdditional,
)
from api.v1.modules.a76.pedmientos.models.pedimento_config_calculations import (
PedimentoConfigCalculations,
)
from api.v1.modules.a76.pedmientos.models.pedimento_config_parameters import (
PedimentoConfigParameters,
)
from api.v1.modules.a76.pedmientos.models.pedimento_config_surcharges import (
PedimentoConfigSurcharges,
)
from api.v1.modules.a76.pedmientos.models.pedimento_config_update_rectification import (
PedimentoConfigUpdateRectification,
)
from api.v1.modules.a76.pedmientos.models.pedimento_config_updates import (
PedimentoConfigUpdates,
)
from api.v1.modules.a76.pedmientos.models.pedimento_customs_offices import (
PedimentoCustomsOffices,
)
from api.v1.modules.a76.pedmientos.models.pedimento_dates import PedimentoDates
from api.v1.modules.a76.pedmientos.models.pedimento_decrementables import PedimentoDecrementables
from api.v1.modules.a76.pedmientos.models.pedimento_incrementables import PedimentoIncrementables
from api.v1.modules.a76.pedmientos.models.pedimento_decrementables import (
PedimentoDecrementables,
)
from api.v1.modules.a76.pedmientos.models.pedimento_incrementables import (
PedimentoIncrementables,
)
from api.v1.modules.a76.pedmientos.models.pedimento_indexes import PedimentoIndexes
from api.v1.modules.a76.pedmientos.models.pedimento_payments import PedimentoPayments
from api.v1.modules.a76.pedmientos.models.pedimento_rectification_destination import PedimentoRectificationDestination
from api.v1.modules.a76.pedmientos.models.pedimento_rectification_origin import PedimentoRectificationOrigin
from api.v1.modules.a76.pedmientos.models.pedimento_transport_means import PedimentoTransportMeans
from api.v1.modules.a76.pedmientos.models.pedimento_validation import PedimentoValidation
from api.v1.modules.a76.pedmientos.models.pedimento_payments import (
PedimentoPayments,
)
from api.v1.modules.a76.pedmientos.models.pedimento_rectification_destination import (
PedimentoRectificationDestination,
)
from api.v1.modules.a76.pedmientos.models.pedimento_rectification_origin import (
PedimentoRectificationOrigin,
)
from api.v1.modules.a76.pedmientos.models.pedimento_transport_means import (
PedimentoTransportMeans,
)
from api.v1.modules.a76.pedmientos.models.pedimento_validation import (
PedimentoValidation,
)
class Pedimentos(Base):
__tablename__ = 'pedimentos'
__tablename__ = "pedimentos"
__table_args__ = (
PrimaryKeyConstraint('id', name='pedimentos_pkey'),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimentos_tenant'),
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimentos_company'),
ForeignKeyConstraint(['client_id'], ['a76.client_provider.id'], name='fk_pedimentos_client'),
ForeignKeyConstraint(['regime'], ['public.pedimento_regimens.code'], name='fk_pedimentos_regime'),
ForeignKeyConstraint(['pedimento_code'], ['public.pedimento_codes.code'], name='fk_pedimentos_code'),
UniqueConstraint('tenant_id', 'company_id', 'year', 'customs_office', 'license', 'pedimento_number', name='pedimentos_unique_key'),
Index('idx_pedimentos_client_id', 'client_id'),
Index('idx_pedimentos_created_at', 'created_at'),
Index('idx_pedimentos_status', 'status'),
{'schema': 'a76'}
PrimaryKeyConstraint("id", name="pedimentos_pkey"),
ForeignKeyConstraint(
["tenant_id"], ["a76.tenants.id"], name="fk_pedimentos_tenant"
),
ForeignKeyConstraint(
["company_id"], ["a76.company.id"], name="fk_pedimentos_company"
),
ForeignKeyConstraint(
["client_id"], ["a76.client_provider.id"], name="fk_pedimentos_client"
),
ForeignKeyConstraint(
["regime"], ["public.pedimento_regimens.code"], name="fk_pedimentos_regime"
),
ForeignKeyConstraint(
["pedimento_code"],
["public.pedimento_codes.code"],
name="fk_pedimentos_code",
),
UniqueConstraint(
"tenant_id",
"company_id",
"year",
"customs_office",
"license",
"pedimento_number",
name="pedimentos_unique_key",
),
Index("idx_pedimentos_client_id", "client_id"),
Index("idx_pedimentos_created_at", "created_at"),
Index("idx_pedimentos_status", "status"),
{"schema": "a76"},
)
id: Mapped[int] = mapped_column(Integer)
@@ -61,24 +121,67 @@ class Pedimentos(Base):
exchange_rate: Mapped[Optional[Decimal]] = mapped_column(Numeric(9, 5))
# Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
created_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now()
)
updated_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now(), onupdate=func.now()
)
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
pedimento_config_additional: Mapped['PedimentoConfigAdditional'] = relationship('PedimentoConfigAdditional', uselist=False, back_populates='pedimento')
pedimento_config_calculations: Mapped['PedimentoConfigCalculations'] = relationship('PedimentoConfigCalculations', uselist=False, back_populates='pedimento')
pedimento_config_parameters: Mapped['PedimentoConfigParameters'] = relationship('PedimentoConfigParameters', uselist=False, back_populates='pedimento')
pedimento_config_surcharges: Mapped['PedimentoConfigSurcharges'] = relationship('PedimentoConfigSurcharges', uselist=False, back_populates='pedimento')
pedimento_config_update_rectification: Mapped['PedimentoConfigUpdateRectification'] = relationship('PedimentoConfigUpdateRectification', uselist=False, back_populates='pedimento')
pedimento_config_updates: Mapped['PedimentoConfigUpdates'] = relationship('PedimentoConfigUpdates', uselist=False, back_populates='pedimento')
pedimento_customs_offices: Mapped['PedimentoCustomsOffices'] = relationship('PedimentoCustomsOffices', uselist=False, back_populates='pedimento')
pedimento_dates: Mapped['PedimentoDates'] = relationship('PedimentoDates', uselist=False, back_populates='pedimento')
pedimento_decrementables: Mapped['PedimentoDecrementables'] = relationship('PedimentoDecrementables', uselist=False, back_populates='pedimento')
pedimento_incrementables: Mapped['PedimentoIncrementables'] = relationship('PedimentoIncrementables', uselist=False, back_populates='pedimento')
pedimento_indexes: Mapped['PedimentoIndexes'] = relationship('PedimentoIndexes', uselist=False, back_populates='pedimento')
pedimento_payments: Mapped['PedimentoPayments'] = relationship('PedimentoPayments', uselist=False, back_populates='pedimento')
pedimento_rectification_destination: Mapped['PedimentoRectificationDestination'] = relationship('PedimentoRectificationDestination', uselist=False, back_populates='pedimento')
pedimento_rectification_origin: Mapped['PedimentoRectificationOrigin'] = relationship('PedimentoRectificationOrigin', uselist=False, back_populates='pedimento')
pedimento_transport_means: Mapped['PedimentoTransportMeans'] = relationship('PedimentoTransportMeans', uselist=False, back_populates='pedimento')
pedimento_validation: Mapped['PedimentoValidation'] = relationship('PedimentoValidation', uselist=False, back_populates='pedimento')
pedimento_config_additional: Mapped["PedimentoConfigAdditional"] = relationship(
"PedimentoConfigAdditional", uselist=False, back_populates="pedimento"
)
pedimento_config_calculations: Mapped["PedimentoConfigCalculations"] = relationship(
"PedimentoConfigCalculations", uselist=False, back_populates="pedimento"
)
pedimento_config_parameters: Mapped["PedimentoConfigParameters"] = relationship(
"PedimentoConfigParameters", uselist=False, back_populates="pedimento"
)
pedimento_config_surcharges: Mapped["PedimentoConfigSurcharges"] = relationship(
"PedimentoConfigSurcharges", uselist=False, back_populates="pedimento"
)
pedimento_config_update_rectification: Mapped[
"PedimentoConfigUpdateRectification"
] = relationship(
"PedimentoConfigUpdateRectification", uselist=False, back_populates="pedimento"
)
pedimento_config_updates: Mapped["PedimentoConfigUpdates"] = relationship(
"PedimentoConfigUpdates", uselist=False, back_populates="pedimento"
)
pedimento_customs_offices: Mapped["PedimentoCustomsOffices"] = relationship(
"PedimentoCustomsOffices", uselist=False, back_populates="pedimento"
)
pedimento_dates: Mapped["PedimentoDates"] = relationship(
"PedimentoDates", uselist=False, back_populates="pedimento"
)
pedimento_decrementables: Mapped["PedimentoDecrementables"] = relationship(
"PedimentoDecrementables", uselist=False, back_populates="pedimento"
)
pedimento_incrementables: Mapped["PedimentoIncrementables"] = relationship(
"PedimentoIncrementables", uselist=False, back_populates="pedimento"
)
pedimento_indexes: Mapped["PedimentoIndexes"] = relationship(
"PedimentoIndexes", uselist=False, back_populates="pedimento"
)
pedimento_payments: Mapped["PedimentoPayments"] = relationship(
"PedimentoPayments", uselist=False, back_populates="pedimento"
)
pedimento_rectification_destination: Mapped["PedimentoRectificationDestination"] = (
relationship(
"PedimentoRectificationDestination",
uselist=False,
back_populates="pedimento",
)
)
pedimento_rectification_origin: Mapped["PedimentoRectificationOrigin"] = (
relationship(
"PedimentoRectificationOrigin", uselist=False, back_populates="pedimento"
)
)
pedimento_transport_means: Mapped["PedimentoTransportMeans"] = relationship(
"PedimentoTransportMeans", uselist=False, back_populates="pedimento"
)
pedimento_validation: Mapped["PedimentoValidation"] = relationship(
"PedimentoValidation", uselist=False, back_populates="pedimento"
)

View File

@@ -1,10 +1,20 @@
from fastapi import APIRouter
from .routes.pedimento_config_additional import router as pedimento_config_additional_router
from .routes.pedimento_config_calculations import router as pedimento_config_calculations_router
from .routes.pedimento_config_parameters import router as pedimento_config_parameters_router
from .routes.pedimento_config_surcharges import router as pedimento_config_surcharges_router
from .routes.pedimento_config_update_rectification import router as pedimento_config_update_rectification_router
from .routes.pedimento_config_additional import (
router as pedimento_config_additional_router,
)
from .routes.pedimento_config_calculations import (
router as pedimento_config_calculations_router,
)
from .routes.pedimento_config_parameters import (
router as pedimento_config_parameters_router,
)
from .routes.pedimento_config_surcharges import (
router as pedimento_config_surcharges_router,
)
from .routes.pedimento_config_update_rectification import (
router as pedimento_config_update_rectification_router,
)
from .routes.pedimento_config_updates import router as pedimento_config_updates_router
from .routes.pedimento_customs_offices import router as pedimento_customs_offices_router
from .routes.pedimento_dates import router as pedimento_dates_router
@@ -12,28 +22,98 @@ from .routes.pedimento_decrementables import router as pedimento_decrementables_
from .routes.pedimento_incrementables import router as pedimento_incrementables_router
from .routes.pedimento_indexes import router as pedimento_indexes_router
from .routes.pedimento_payments import router as pedimento_payments_router
from .routes.pedimento_rectification_destination import router as pedimento_rectification_destination_router
from .routes.pedimento_rectification_origin import router as pedimento_rectification_origin_router
from .routes.pedimento_rectification_destination import (
router as pedimento_rectification_destination_router,
)
from .routes.pedimento_rectification_origin import (
router as pedimento_rectification_origin_router,
)
from .routes.pedimento_transport_means import router as pedimento_transport_means_router
from .routes.pedimento_validation import router as pedimento_validation_router
from .routes.pedimentos import router as pedimentos_router
router = APIRouter()
router.include_router(pedimento_config_additional_router, prefix="/pedimentos", tags=["a76 / pedimentos / pedimento_config_additional"])
router.include_router(pedimento_config_calculations_router, prefix="/pedimentos", tags=["a76 / pedimentos / pedimento_config_calculations"])
router.include_router(pedimento_config_parameters_router, prefix="/pedimentos", tags=["a76 / pedimentos / pedimento_config_parameters"])
router.include_router(pedimento_config_surcharges_router, prefix="/pedimentos", tags=["a76 / pedimentos / pedimento_config_surcharges"])
router.include_router(pedimento_config_update_rectification_router, prefix="/pedimentos", tags=["a76 / pedimentos / pedimento_config_update_rectification"])
router.include_router(pedimento_config_updates_router, prefix="/pedimentos", tags=["a76 / pedimentos / pedimento_config_updates"])
router.include_router(pedimento_customs_offices_router, prefix="/pedimentos", tags=["a76 / pedimentos / pedimento_customs_offices"])
router.include_router(pedimento_dates_router, prefix="/pedimentos", tags=["a76 / pedimentos / pedimento_dates"])
router.include_router(pedimento_decrementables_router, prefix="/pedimentos", tags=["a76 / pedimentos / pedimento_decrementables"])
router.include_router(pedimento_incrementables_router, prefix="/pedimentos", tags=["a76 / pedimentos / pedimento_incrementables"])
router.include_router(pedimento_indexes_router, prefix="/pedimentos", tags=["a76 / pedimentos / pedimento_indexes"])
router.include_router(pedimento_payments_router, prefix="/pedimentos", tags=["a76 / pedimentos / pedimento_payments"])
router.include_router(pedimento_rectification_destination_router, prefix="/pedimentos", tags=["a76 / pedimentos / pedimento_rectification_destination"])
router.include_router(pedimento_rectification_origin_router, prefix="/pedimentos", tags=["a76 / pedimentos / pedimento_rectification_origin"])
router.include_router(pedimento_transport_means_router, prefix="/pedimentos", tags=["a76 / pedimentos / pedimento_transport_means"])
router.include_router(pedimento_validation_router, prefix="/pedimentos", tags=["a76 / pedimentos / pedimento_validation"])
router.include_router(pedimentos_router, prefix="/pedimentos", tags=["a76 / pedimentos"])
router.include_router(
pedimento_config_additional_router,
prefix="/pedimentos",
tags=["a76 / pedimentos / pedimento_config_additional"],
)
router.include_router(
pedimento_config_calculations_router,
prefix="/pedimentos",
tags=["a76 / pedimentos / pedimento_config_calculations"],
)
router.include_router(
pedimento_config_parameters_router,
prefix="/pedimentos",
tags=["a76 / pedimentos / pedimento_config_parameters"],
)
router.include_router(
pedimento_config_surcharges_router,
prefix="/pedimentos",
tags=["a76 / pedimentos / pedimento_config_surcharges"],
)
router.include_router(
pedimento_config_update_rectification_router,
prefix="/pedimentos",
tags=["a76 / pedimentos / pedimento_config_update_rectification"],
)
router.include_router(
pedimento_config_updates_router,
prefix="/pedimentos",
tags=["a76 / pedimentos / pedimento_config_updates"],
)
router.include_router(
pedimento_customs_offices_router,
prefix="/pedimentos",
tags=["a76 / pedimentos / pedimento_customs_offices"],
)
router.include_router(
pedimento_dates_router,
prefix="/pedimentos",
tags=["a76 / pedimentos / pedimento_dates"],
)
router.include_router(
pedimento_decrementables_router,
prefix="/pedimentos",
tags=["a76 / pedimentos / pedimento_decrementables"],
)
router.include_router(
pedimento_incrementables_router,
prefix="/pedimentos",
tags=["a76 / pedimentos / pedimento_incrementables"],
)
router.include_router(
pedimento_indexes_router,
prefix="/pedimentos",
tags=["a76 / pedimentos / pedimento_indexes"],
)
router.include_router(
pedimento_payments_router,
prefix="/pedimentos",
tags=["a76 / pedimentos / pedimento_payments"],
)
router.include_router(
pedimento_rectification_destination_router,
prefix="/pedimentos",
tags=["a76 / pedimentos / pedimento_rectification_destination"],
)
router.include_router(
pedimento_rectification_origin_router,
prefix="/pedimentos",
tags=["a76 / pedimentos / pedimento_rectification_origin"],
)
router.include_router(
pedimento_transport_means_router,
prefix="/pedimentos",
tags=["a76 / pedimentos / pedimento_transport_means"],
)
router.include_router(
pedimento_validation_router,
prefix="/pedimentos",
tags=["a76 / pedimentos / pedimento_validation"],
)
router.include_router(
pedimentos_router, prefix="/pedimentos", tags=["a76 / pedimentos"]
)

View File

@@ -1,16 +1,18 @@
"""
Routes for PedimentoConfigAdditional CRUD operations
"""
from typing import Dict, Any
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
from core.security import validate_access_to_resource
from core.security import validate_access_to_resource, get_current_user
from ..services.pedimento_config_additional import PedimentoConfigAdditionalService
from ..dtos.pedimento_config_additional import (
PedimentoConfigAdditionalCreate,
PedimentoConfigAdditionalUpdate,
PedimentoConfigAdditionalResponse
PedimentoConfigAdditionalResponse,
)
@@ -22,11 +24,14 @@ async def get_config_additional(
pedimento_id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Get config additional by pedimento ID"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
config = PedimentoConfigAdditionalService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id)
config = PedimentoConfigAdditionalService.get_by_pedimento_id(
db, pedimento_id, tenant_id, company_id
)
if not config:
raise HTTPException(status_code=404, detail="Config additional not found")
@@ -39,9 +44,10 @@ async def create_config_additional(
data: PedimentoConfigAdditionalCreate,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Create config additional"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
# Ensure pedimento_id and company_id match
if data.pedimento_id != pedimento_id:
@@ -57,11 +63,14 @@ async def update_config_additional(
data: PedimentoConfigAdditionalUpdate,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Update config additional"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
config = PedimentoConfigAdditionalService.update(db, pedimento_id, tenant_id, company_id, data)
config = PedimentoConfigAdditionalService.update(
db, pedimento_id, tenant_id, company_id, data
)
if not config:
raise HTTPException(status_code=404, detail="Config additional not found")
@@ -73,11 +82,14 @@ async def delete_config_additional(
pedimento_id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Delete config additional"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
success = PedimentoConfigAdditionalService.delete(db, pedimento_id, tenant_id, company_id)
success = PedimentoConfigAdditionalService.delete(
db, pedimento_id, tenant_id, company_id
)
if not success:
raise HTTPException(status_code=404, detail="Config additional not found")

View File

@@ -1,16 +1,18 @@
"""
Routes for PedimentoConfigCalculations CRUD operations
"""
from typing import Dict, Any
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
from core.security import validate_access_to_resource
from core.security import validate_access_to_resource, get_current_user
from ..services.pedimento_config_calculations import PedimentoConfigCalculationsService
from ..dtos.pedimento_config_calculations import (
PedimentoConfigCalculationsCreate,
PedimentoConfigCalculationsUpdate,
PedimentoConfigCalculationsResponse
PedimentoConfigCalculationsResponse,
)
@@ -22,11 +24,14 @@ async def get_config_calculations(
pedimento_id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Get config calculations by pedimento ID"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
config = PedimentoConfigCalculationsService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id)
config = PedimentoConfigCalculationsService.get_by_pedimento_id(
db, pedimento_id, tenant_id, company_id
)
if not config:
raise HTTPException(status_code=404, detail="Config calculations not found")
@@ -39,9 +44,10 @@ async def create_config_calculations(
data: PedimentoConfigCalculationsCreate,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Create config calculations"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
# Ensure pedimento_id matches
if data.pedimento_id != pedimento_id:
@@ -57,11 +63,14 @@ async def update_config_calculations(
data: PedimentoConfigCalculationsUpdate,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Update config calculations"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
config = PedimentoConfigCalculationsService.update(db, pedimento_id, tenant_id, company_id, data)
config = PedimentoConfigCalculationsService.update(
db, pedimento_id, tenant_id, company_id, data
)
if not config:
raise HTTPException(status_code=404, detail="Config calculations not found")
@@ -73,9 +82,10 @@ async def delete_config_calculations(
pedimento_id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Delete config calculations"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
success = PedimentoConfigCalculationsService.delete(db, pedimento_id, company_id)
if not success:

View File

@@ -1,16 +1,18 @@
"""
Routes for PedimentoConfigParameters CRUD operations
"""
from typing import Dict, Any
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
from core.security import validate_access_to_resource
from core.security import validate_access_to_resource, get_current_user
from ..services.pedimento_config_parameters import PedimentoConfigParametersService
from ..dtos.pedimento_config_parameters import (
PedimentoConfigParametersCreate,
PedimentoConfigParametersUpdate,
PedimentoConfigParametersResponse
PedimentoConfigParametersResponse,
)
@@ -22,11 +24,14 @@ async def get_config_parameters(
pedimento_id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Get config parameters by pedimento ID"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
config = PedimentoConfigParametersService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id)
config = PedimentoConfigParametersService.get_by_pedimento_id(
db, pedimento_id, tenant_id, company_id
)
if not config:
raise HTTPException(status_code=404, detail="Config parameters not found")
@@ -39,9 +44,10 @@ async def create_config_parameters(
data: PedimentoConfigParametersCreate,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Create config parameters"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
# Ensure pedimento_id matches
if data.pedimento_id != pedimento_id:
@@ -57,11 +63,14 @@ async def update_config_parameters(
data: PedimentoConfigParametersUpdate,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Update config parameters"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
config = PedimentoConfigParametersService.update(db, pedimento_id, tenant_id, company_id, data)
config = PedimentoConfigParametersService.update(
db, pedimento_id, tenant_id, company_id, data
)
if not config:
raise HTTPException(status_code=404, detail="Config parameters not found")
@@ -73,11 +82,14 @@ async def delete_config_parameters(
pedimento_id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Delete config parameters"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
success = PedimentoConfigParametersService.delete(db, pedimento_id, tenant_id, company_id)
success = PedimentoConfigParametersService.delete(
db, pedimento_id, tenant_id, company_id
)
if not success:
raise HTTPException(status_code=404, detail="Config parameters not found")

View File

@@ -1,16 +1,18 @@
"""
Routes for PedimentoConfigSurcharges CRUD operations
"""
from typing import Dict, Any
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
from core.security import validate_access_to_resource
from core.security import validate_access_to_resource, get_current_user
from ..services.pedimento_config_surcharges import PedimentoConfigSurchargesService
from ..dtos.pedimento_config_surcharges import (
PedimentoConfigSurchargesCreate,
PedimentoConfigSurchargesUpdate,
PedimentoConfigSurchargesResponse
PedimentoConfigSurchargesResponse,
)
@@ -22,11 +24,14 @@ async def get_config_surcharges(
pedimento_id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Get config surcharges by pedimento ID"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
config = PedimentoConfigSurchargesService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id)
config = PedimentoConfigSurchargesService.get_by_pedimento_id(
db, pedimento_id, tenant_id, company_id
)
if not config:
raise HTTPException(status_code=404, detail="Config surcharges not found")
@@ -39,9 +44,10 @@ async def create_config_surcharges(
data: PedimentoConfigSurchargesCreate,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Create config surcharges"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
# Ensure pedimento_id matches
if data.pedimento_id != pedimento_id:
@@ -57,11 +63,14 @@ async def update_config_surcharges(
data: PedimentoConfigSurchargesUpdate,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Update config surcharges"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
config = PedimentoConfigSurchargesService.update(db, pedimento_id, tenant_id, company_id, data)
config = PedimentoConfigSurchargesService.update(
db, pedimento_id, tenant_id, company_id, data
)
if not config:
raise HTTPException(status_code=404, detail="Config surcharges not found")
@@ -73,11 +82,14 @@ async def delete_config_surcharges(
pedimento_id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Delete config surcharges"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
success = PedimentoConfigSurchargesService.delete(db, pedimento_id, tenant_id, company_id)
success = PedimentoConfigSurchargesService.delete(
db, pedimento_id, tenant_id, company_id
)
if not success:
raise HTTPException(status_code=404, detail="Config surcharges not found")

View File

@@ -1,16 +1,20 @@
"""
Routes for PedimentoConfigUpdateRectification CRUD operations
"""
from typing import Dict, Any
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
from core.security import validate_access_to_resource
from core.security import validate_access_to_resource, get_current_user
from ..services.pedimento_config_update_rectification import PedimentoConfigUpdateRectificationService
from ..services.pedimento_config_update_rectification import (
PedimentoConfigUpdateRectificationService,
)
from ..dtos.pedimento_config_update_rectification import (
PedimentoConfigUpdateRectificationCreate,
PedimentoConfigUpdateRectificationUpdate,
PedimentoConfigUpdateRectificationResponse
PedimentoConfigUpdateRectificationResponse,
)
@@ -22,32 +26,42 @@ async def get_config_update_rectification(
pedimento_id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Get config update rectification by pedimento ID"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
config = PedimentoConfigUpdateRectificationService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id)
config = PedimentoConfigUpdateRectificationService.get_by_pedimento_id(
db, pedimento_id, tenant_id, company_id
)
if not config:
raise HTTPException(status_code=404, detail="Config update rectification not found")
raise HTTPException(
status_code=404, detail="Config update rectification not found"
)
return config
@router.post("/", response_model=PedimentoConfigUpdateRectificationResponse, status_code=201)
@router.post(
"/", response_model=PedimentoConfigUpdateRectificationResponse, status_code=201
)
async def create_config_update_rectification(
pedimento_id: int,
data: PedimentoConfigUpdateRectificationCreate,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Create config update rectification"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
# Ensure pedimento_id matches
if data.pedimento_id != pedimento_id:
raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
config = PedimentoConfigUpdateRectificationService.create(db, data, tenant_id, company_id)
config = PedimentoConfigUpdateRectificationService.create(
db, data, tenant_id, company_id
)
return config
@@ -57,13 +71,18 @@ async def update_config_update_rectification(
data: PedimentoConfigUpdateRectificationUpdate,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Update config update rectification"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
config = PedimentoConfigUpdateRectificationService.update(db, pedimento_id, tenant_id, company_id, data)
config = PedimentoConfigUpdateRectificationService.update(
db, pedimento_id, tenant_id, company_id, data
)
if not config:
raise HTTPException(status_code=404, detail="Config update rectification not found")
raise HTTPException(
status_code=404, detail="Config update rectification not found"
)
return config
@@ -73,12 +92,17 @@ async def delete_config_update_rectification(
pedimento_id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Delete config update rectification"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
success = PedimentoConfigUpdateRectificationService.delete(db, pedimento_id, tenant_id, company_id)
success = PedimentoConfigUpdateRectificationService.delete(
db, pedimento_id, tenant_id, company_id
)
if not success:
raise HTTPException(status_code=404, detail="Config update rectification not found")
raise HTTPException(
status_code=404, detail="Config update rectification not found"
)
return None

View File

@@ -1,16 +1,18 @@
"""
Routes for PedimentoConfigUpdates CRUD operations
"""
from typing import Dict, Any
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
from core.security import validate_access_to_resource
from core.security import validate_access_to_resource, get_current_user
from ..services.pedimento_config_updates import PedimentoConfigUpdatesService
from ..dtos.pedimento_config_updates import (
PedimentoConfigUpdatesCreate,
PedimentoConfigUpdatesUpdate,
PedimentoConfigUpdatesResponse
PedimentoConfigUpdatesResponse,
)
@@ -22,11 +24,14 @@ async def get_config_updates(
pedimento_id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Get config updates by pedimento ID"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
config = PedimentoConfigUpdatesService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id)
config = PedimentoConfigUpdatesService.get_by_pedimento_id(
db, pedimento_id, tenant_id, company_id
)
if not config:
raise HTTPException(status_code=404, detail="Config updates not found")
@@ -38,10 +43,11 @@ async def create_config_updates(
pedimento_id: int,
data: PedimentoConfigUpdatesCreate,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db)
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Create config updates"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
# Ensure pedimento_id matches
if data.pedimento_id != pedimento_id:
@@ -57,11 +63,14 @@ async def update_config_updates(
data: PedimentoConfigUpdatesUpdate,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Update config updates"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
config = PedimentoConfigUpdatesService.update(db, pedimento_id, tenant_id, company_id, data)
config = PedimentoConfigUpdatesService.update(
db, pedimento_id, tenant_id, company_id, data
)
if not config:
raise HTTPException(status_code=404, detail="Config updates not found")
@@ -73,11 +82,14 @@ async def delete_config_updates(
pedimento_id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Delete config updates"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
success = PedimentoConfigUpdatesService.delete(db, pedimento_id, tenant_id, company_id)
success = PedimentoConfigUpdatesService.delete(
db, pedimento_id, tenant_id, company_id
)
if not success:
raise HTTPException(status_code=404, detail="Config updates not found")

View File

@@ -1,16 +1,17 @@
"""
Routes for PedimentoCustomsOffices CRUD operations
"""
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from typing import List
from typing import Dict, Any, List
from core.database import get_core_db
from core.security import validate_access_to_resource
from core.security import validate_access_to_resource, get_current_user
from ..services.pedimento_customs_offices import PedimentoCustomsOfficesService
from ..dtos.pedimento_customs_offices import (
PedimentoCustomsOfficesCreate,
PedimentoCustomsOfficesUpdate,
PedimentoCustomsOfficesResponse
PedimentoCustomsOfficesResponse,
)
@@ -22,11 +23,14 @@ async def list_customs_offices(
pedimento_id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Get all customs offices for a pedimento"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
offices = PedimentoCustomsOfficesService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id)
offices = PedimentoCustomsOfficesService.get_by_pedimento_id(
db, pedimento_id, tenant_id, company_id
)
return offices
@@ -36,11 +40,14 @@ async def get_customs_office(
office_id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Get a specific customs office by ID"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
office = PedimentoCustomsOfficesService.get_by_id(db, office_id, pedimento_id, tenant_id, company_id)
office = PedimentoCustomsOfficesService.get_by_id(
db, office_id, pedimento_id, tenant_id, company_id
)
if not office:
raise HTTPException(status_code=404, detail="Customs office not found")
@@ -53,9 +60,10 @@ async def create_customs_office(
data: PedimentoCustomsOfficesCreate,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Create a new customs office"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
# Ensure pedimento_id matches
if data.pedimento_id != pedimento_id:
@@ -72,11 +80,14 @@ async def update_customs_office(
data: PedimentoCustomsOfficesUpdate,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Update a customs office"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
office = PedimentoCustomsOfficesService.update(db, office_id, pedimento_id, tenant_id, company_id, data)
office = PedimentoCustomsOfficesService.update(
db, office_id, pedimento_id, tenant_id, company_id, data
)
if not office:
raise HTTPException(status_code=404, detail="Customs office not found")
@@ -89,11 +100,14 @@ async def delete_customs_office(
office_id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Delete a customs office"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
success = PedimentoCustomsOfficesService.delete(db, office_id, pedimento_id, tenant_id, company_id)
success = PedimentoCustomsOfficesService.delete(
db, office_id, pedimento_id, tenant_id, company_id
)
if not success:
raise HTTPException(status_code=404, detail="Customs office not found")

View File

@@ -1,33 +1,39 @@
"""
Routes for PedimentoDates CRUD operations
"""
import logging
from typing import Dict, Any
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
from core.security import validate_access_to_resource
from core.security import validate_access_to_resource, get_current_user
from ..services.pedimento_dates import PedimentoDatesService
from ..dtos.pedimento_dates import (
PedimentoDatesCreate,
PedimentoDatesUpdate,
PedimentoDatesResponse
PedimentoDatesResponse,
)
router = APIRouter(prefix="/{pedimento_id}/dates")
logger = logging.getLogger(__name__)
@router.get("/", response_model=PedimentoDatesResponse)
async def get_dates(
pedimento_id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Get dates by pedimento ID"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
dates = PedimentoDatesService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id)
dates = PedimentoDatesService.get_by_pedimento_id(
db, pedimento_id, tenant_id, company_id
)
if not dates:
raise HTTPException(status_code=404, detail="Pedimento dates not found")
@@ -39,9 +45,10 @@ async def create_dates(
data: PedimentoDatesCreate,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Create pedimento dates"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
dates = PedimentoDatesService.create(db, data, tenant_id, company_id)
return dates
@@ -53,9 +60,10 @@ async def update_dates(
data: PedimentoDatesUpdate,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Update pedimento dates"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
dates = PedimentoDatesService.update(db, pedimento_id, tenant_id, company_id, data)
if not dates:
@@ -69,9 +77,10 @@ async def delete_dates(
pedimento_id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Delete pedimento dates"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
success = PedimentoDatesService.delete(db, pedimento_id, tenant_id, company_id)
if not success:

View File

@@ -1,17 +1,18 @@
"""
Routes for PedimentoDecrementables CRUD operations
"""
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from typing import List
from typing import Dict, Any, List
from core.database import get_core_db
from core.security import validate_access_to_resource
from core.security import validate_access_to_resource, get_current_user
from ..services.pedimento_decrementables import PedimentoDecrementablesService
from ..dtos.pedimento_decrementables import (
PedimentoDecrementablesCreate,
PedimentoDecrementablesUpdate,
PedimentoDecrementablesResponse
PedimentoDecrementablesResponse,
)
@@ -23,11 +24,14 @@ async def list_decrementables(
pedimento_id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Get all decrementables for a pedimento"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
decrementables = PedimentoDecrementablesService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id)
decrementables = PedimentoDecrementablesService.get_by_pedimento_id(
db, pedimento_id, tenant_id, company_id
)
return decrementables
@@ -37,11 +41,14 @@ async def get_decrementable(
decrementable_id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Get a specific decrementable by ID"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
decrementable = PedimentoDecrementablesService.get_by_id(db, decrementable_id, pedimento_id, tenant_id, company_id)
decrementable = PedimentoDecrementablesService.get_by_id(
db, decrementable_id, pedimento_id, tenant_id, company_id
)
if not decrementable:
raise HTTPException(status_code=404, detail="Decrementable not found")
@@ -54,15 +61,18 @@ async def create_decrementable(
data: PedimentoDecrementablesCreate,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Create a new decrementable"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
# Ensure pedimento_id matches
if data.pedimento_id != pedimento_id:
raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
decrementable = PedimentoDecrementablesService.create(db, data, tenant_id, company_id)
decrementable = PedimentoDecrementablesService.create(
db, data, tenant_id, company_id
)
return decrementable
@@ -73,11 +83,14 @@ async def update_decrementable(
data: PedimentoDecrementablesUpdate,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Update a decrementable"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
decrementable = PedimentoDecrementablesService.update(db, decrementable_id, pedimento_id, tenant_id, company_id, data)
decrementable = PedimentoDecrementablesService.update(
db, decrementable_id, pedimento_id, tenant_id, company_id, data
)
if not decrementable:
raise HTTPException(status_code=404, detail="Decrementable not found")
@@ -90,11 +103,14 @@ async def delete_decrementable(
decrementable_id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Delete a decrementable"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
success = PedimentoDecrementablesService.delete(db, decrementable_id, pedimento_id, tenant_id, company_id)
success = PedimentoDecrementablesService.delete(
db, decrementable_id, pedimento_id, tenant_id, company_id
)
if not success:
raise HTTPException(status_code=404, detail="Decrementable not found")

View File

@@ -1,17 +1,18 @@
"""
Routes for PedimentoIncrementables CRUD operations
"""
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from typing import List
from typing import Dict, Any, List
from core.database import get_core_db
from core.security import validate_access_to_resource
from core.security import validate_access_to_resource, get_current_user
from ..services.pedimento_incrementables import PedimentoIncrementablesService
from ..dtos.pedimento_incrementables import (
PedimentoIncrementablesCreate,
PedimentoIncrementablesUpdate,
PedimentoIncrementablesResponse
PedimentoIncrementablesResponse,
)
@@ -23,11 +24,14 @@ async def list_incrementables(
pedimento_id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Get all incrementables for a pedimento"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
incrementables = PedimentoIncrementablesService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id)
incrementables = PedimentoIncrementablesService.get_by_pedimento_id(
db, pedimento_id, tenant_id, company_id
)
return incrementables
@@ -37,11 +41,14 @@ async def get_incrementable(
incrementable_id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Get a specific incrementable by ID"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
incrementable = PedimentoIncrementablesService.get_by_id(db, incrementable_id, pedimento_id, tenant_id, company_id)
incrementable = PedimentoIncrementablesService.get_by_id(
db, incrementable_id, pedimento_id, tenant_id, company_id
)
if not incrementable:
raise HTTPException(status_code=404, detail="Incrementable not found")
@@ -54,15 +61,18 @@ async def create_incrementable(
data: PedimentoIncrementablesCreate,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Create a new incrementable"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
# Ensure pedimento_id matches
if data.pedimento_id != pedimento_id:
raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
incrementable = PedimentoIncrementablesService.create(db, data, tenant_id, company_id)
incrementable = PedimentoIncrementablesService.create(
db, data, tenant_id, company_id
)
return incrementable
@@ -73,11 +83,14 @@ async def update_incrementable(
data: PedimentoIncrementablesUpdate,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Update an incrementable"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
incrementable = PedimentoIncrementablesService.update(db, incrementable_id, pedimento_id, tenant_id, company_id, data)
incrementable = PedimentoIncrementablesService.update(
db, incrementable_id, pedimento_id, tenant_id, company_id, data
)
if not incrementable:
raise HTTPException(status_code=404, detail="Incrementable not found")
@@ -90,11 +103,14 @@ async def delete_incrementable(
incrementable_id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Delete an incrementable"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
success = PedimentoIncrementablesService.delete(db, incrementable_id, pedimento_id, tenant_id, company_id)
success = PedimentoIncrementablesService.delete(
db, incrementable_id, pedimento_id, tenant_id, company_id
)
if not success:
raise HTTPException(status_code=404, detail="Incrementable not found")

View File

@@ -1,16 +1,18 @@
"""
Routes for PedimentoIndexes CRUD operations
"""
from typing import Dict, Any
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
from core.security import validate_access_to_resource
from core.security import validate_access_to_resource, get_current_user
from ..services.pedimento_indexes import PedimentoIndexesService
from ..dtos.pedimento_indexes import (
PedimentoIndexesCreate,
PedimentoIndexesUpdate,
PedimentoIndexesResponse
PedimentoIndexesResponse,
)
@@ -22,11 +24,14 @@ async def get_indexes(
pedimento_id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Get indexes by pedimento ID"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
indexes = PedimentoIndexesService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id)
indexes = PedimentoIndexesService.get_by_pedimento_id(
db, pedimento_id, tenant_id, company_id
)
if not indexes:
raise HTTPException(status_code=404, detail="Pedimento indexes not found")
@@ -39,9 +44,10 @@ async def create_indexes(
data: PedimentoIndexesCreate,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Create pedimento indexes"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
# Ensure pedimento_id matches
if data.pedimento_id != pedimento_id:
@@ -57,11 +63,14 @@ async def update_indexes(
data: PedimentoIndexesUpdate,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Update pedimento indexes"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
indexes = PedimentoIndexesService.update(db, pedimento_id, tenant_id, company_id, data)
indexes = PedimentoIndexesService.update(
db, pedimento_id, tenant_id, company_id, data
)
if not indexes:
raise HTTPException(status_code=404, detail="Pedimento indexes not found")
@@ -73,9 +82,10 @@ async def delete_indexes(
pedimento_id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Delete pedimento indexes"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
success = PedimentoIndexesService.delete(db, pedimento_id, tenant_id, company_id)
if not success:

View File

@@ -1,17 +1,18 @@
"""
Routes for PedimentoPayments CRUD operations
"""
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from typing import List
from typing import Dict, Any, List
from core.database import get_core_db
from core.security import validate_access_to_resource
from core.security import validate_access_to_resource, get_current_user
from ..services.pedimento_payments import PedimentoPaymentsService
from ..dtos.pedimento_payments import (
PedimentoPaymentsCreate,
PedimentoPaymentsUpdate,
PedimentoPaymentsResponse
PedimentoPaymentsResponse,
)
@@ -23,11 +24,14 @@ async def list_payments(
pedimento_id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Get all payments for a pedimento"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
payments = PedimentoPaymentsService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id)
payments = PedimentoPaymentsService.get_by_pedimento_id(
db, pedimento_id, tenant_id, company_id
)
return payments
@@ -37,11 +41,14 @@ async def get_payment(
payment_id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Get a specific payment by ID"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
payment = PedimentoPaymentsService.get_by_id(db, payment_id, pedimento_id, tenant_id, company_id)
payment = PedimentoPaymentsService.get_by_id(
db, payment_id, pedimento_id, tenant_id, company_id
)
if not payment:
raise HTTPException(status_code=404, detail="Payment not found")
@@ -54,9 +61,10 @@ async def create_payment(
data: PedimentoPaymentsCreate,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Create a new payment"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
# Ensure pedimento_id matches
if data.pedimento_id != pedimento_id:
@@ -73,11 +81,14 @@ async def update_payment(
data: PedimentoPaymentsUpdate,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Update a payment"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
payment = PedimentoPaymentsService.update(db, payment_id, pedimento_id, tenant_id, company_id, data)
payment = PedimentoPaymentsService.update(
db, payment_id, pedimento_id, tenant_id, company_id, data
)
if not payment:
raise HTTPException(status_code=404, detail="Payment not found")
@@ -90,11 +101,14 @@ async def delete_payment(
payment_id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Delete a payment"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
success = PedimentoPaymentsService.delete(db, payment_id, pedimento_id, tenant_id, company_id)
success = PedimentoPaymentsService.delete(
db, payment_id, pedimento_id, tenant_id, company_id
)
if not success:
raise HTTPException(status_code=404, detail="Payment not found")

View File

@@ -1,16 +1,20 @@
"""
Routes for PedimentoRectificationDestination CRUD operations
"""
from typing import Dict, Any
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
from core.security import validate_access_to_resource
from core.security import validate_access_to_resource, get_current_user
from ..services.pedimento_rectification_destination import PedimentoRectificationDestinationService
from ..services.pedimento_rectification_destination import (
PedimentoRectificationDestinationService,
)
from ..dtos.pedimento_rectification_destination import (
PedimentoRectificationDestinationCreate,
PedimentoRectificationDestinationUpdate,
PedimentoRectificationDestinationResponse
PedimentoRectificationDestinationResponse,
)
@@ -22,32 +26,42 @@ async def get_rectification_destination(
pedimento_id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Get rectification destination by pedimento ID"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
rectification = PedimentoRectificationDestinationService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id)
rectification = PedimentoRectificationDestinationService.get_by_pedimento_id(
db, pedimento_id, tenant_id, company_id
)
if not rectification:
raise HTTPException(status_code=404, detail="Rectification destination not found")
raise HTTPException(
status_code=404, detail="Rectification destination not found"
)
return rectification
@router.post("/", response_model=PedimentoRectificationDestinationResponse, status_code=201)
@router.post(
"/", response_model=PedimentoRectificationDestinationResponse, status_code=201
)
async def create_rectification_destination(
pedimento_id: int,
data: PedimentoRectificationDestinationCreate,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Create rectification destination"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
# Ensure pedimento_id matches
if data.pedimento_id != pedimento_id:
raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
rectification = PedimentoRectificationDestinationService.create(db, data, tenant_id, company_id)
rectification = PedimentoRectificationDestinationService.create(
db, data, tenant_id, company_id
)
return rectification
@@ -57,13 +71,18 @@ async def update_rectification_destination(
data: PedimentoRectificationDestinationUpdate,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Update rectification destination"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
rectification = PedimentoRectificationDestinationService.update(db, pedimento_id, tenant_id, company_id, data)
rectification = PedimentoRectificationDestinationService.update(
db, pedimento_id, tenant_id, company_id, data
)
if not rectification:
raise HTTPException(status_code=404, detail="Rectification destination not found")
raise HTTPException(
status_code=404, detail="Rectification destination not found"
)
return rectification
@@ -73,12 +92,17 @@ async def delete_rectification_destination(
pedimento_id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Delete rectification destination"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
success = PedimentoRectificationDestinationService.delete(db, pedimento_id, tenant_id, company_id)
success = PedimentoRectificationDestinationService.delete(
db, pedimento_id, tenant_id, company_id
)
if not success:
raise HTTPException(status_code=404, detail="Rectification destination not found")
raise HTTPException(
status_code=404, detail="Rectification destination not found"
)
return None

View File

@@ -1,16 +1,20 @@
"""
Routes for PedimentoRectificationOrigin CRUD operations
"""
from typing import Dict, Any
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
from core.security import validate_access_to_resource
from core.security import validate_access_to_resource, get_current_user
from ..services.pedimento_rectification_origin import PedimentoRectificationOriginService
from ..services.pedimento_rectification_origin import (
PedimentoRectificationOriginService,
)
from ..dtos.pedimento_rectification_origin import (
PedimentoRectificationOriginCreate,
PedimentoRectificationOriginUpdate,
PedimentoRectificationOriginResponse
PedimentoRectificationOriginResponse,
)
@@ -22,11 +26,14 @@ async def get_rectification_origin(
pedimento_id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Get rectification origin by pedimento ID"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
rectification = PedimentoRectificationOriginService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id)
rectification = PedimentoRectificationOriginService.get_by_pedimento_id(
db, pedimento_id, tenant_id, company_id
)
if not rectification:
raise HTTPException(status_code=404, detail="Rectification origin not found")
@@ -39,15 +46,18 @@ async def create_rectification_origin(
data: PedimentoRectificationOriginCreate,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Create rectification origin"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
# Ensure pedimento_id matches
if data.pedimento_id != pedimento_id:
raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
rectification = PedimentoRectificationOriginService.create(db, data, tenant_id, company_id)
rectification = PedimentoRectificationOriginService.create(
db, data, tenant_id, company_id
)
return rectification
@@ -57,11 +67,14 @@ async def update_rectification_origin(
data: PedimentoRectificationOriginUpdate,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Update rectification origin"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
rectification = PedimentoRectificationOriginService.update(db, pedimento_id, tenant_id, company_id, data)
rectification = PedimentoRectificationOriginService.update(
db, pedimento_id, tenant_id, company_id, data
)
if not rectification:
raise HTTPException(status_code=404, detail="Rectification origin not found")
@@ -73,11 +86,14 @@ async def delete_rectification_origin(
pedimento_id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Delete rectification origin"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
success = PedimentoRectificationOriginService.delete(db, pedimento_id, tenant_id, company_id)
success = PedimentoRectificationOriginService.delete(
db, pedimento_id, tenant_id, company_id
)
if not success:
raise HTTPException(status_code=404, detail="Rectification origin not found")

View File

@@ -1,17 +1,18 @@
"""
Routes for PedimentoTransportMeans CRUD operations
"""
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from typing import List
from typing import Dict, Any, List
from core.database import get_core_db
from core.security import validate_access_to_resource
from core.security import validate_access_to_resource, get_current_user
from ..services.pedimento_transport_means import PedimentoTransportMeansService
from ..dtos.pedimento_transport_means import (
PedimentoTransportMeansCreate,
PedimentoTransportMeansUpdate,
PedimentoTransportMeansResponse
PedimentoTransportMeansResponse,
)
@@ -23,11 +24,14 @@ async def list_transport_means(
pedimento_id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Get all transport means for a pedimento"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
transport_means = PedimentoTransportMeansService.get_by_pedimento_id(db, pedimento_id, tenant_id, company_id)
transport_means = PedimentoTransportMeansService.get_by_pedimento_id(
db, pedimento_id, tenant_id, company_id
)
return transport_means
@@ -37,11 +41,14 @@ async def get_transport_mean(
transport_mean_id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Get a specific transport mean by ID"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
transport_mean = PedimentoTransportMeansService.get_by_id(db, transport_mean_id, pedimento_id, tenant_id, company_id)
transport_mean = PedimentoTransportMeansService.get_by_id(
db, transport_mean_id, pedimento_id, tenant_id, company_id
)
if not transport_mean:
raise HTTPException(status_code=404, detail="Transport mean not found")
@@ -54,15 +61,18 @@ async def create_transport_mean(
data: PedimentoTransportMeansCreate,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Create a new transport mean"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
# Ensure pedimento_id matches
if data.pedimento_id != pedimento_id:
raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
transport_mean = PedimentoTransportMeansService.create(db, data, tenant_id, company_id)
transport_mean = PedimentoTransportMeansService.create(
db, data, tenant_id, company_id
)
return transport_mean
@@ -73,11 +83,14 @@ async def update_transport_mean(
data: PedimentoTransportMeansUpdate,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Update a transport mean"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
transport_mean = PedimentoTransportMeansService.update(db, transport_mean_id, pedimento_id, tenant_id, company_id, data)
transport_mean = PedimentoTransportMeansService.update(
db, transport_mean_id, pedimento_id, tenant_id, company_id, data
)
if not transport_mean:
raise HTTPException(status_code=404, detail="Transport mean not found")
@@ -90,11 +103,14 @@ async def delete_transport_mean(
transport_mean_id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Delete a transport mean"""
tenant_id = validate_access_to_resource(company_id)
tenant_id = validate_access_to_resource(db, company_id, current_user)
success = PedimentoTransportMeansService.delete(db, transport_mean_id, pedimento_id, tenant_id, company_id)
success = PedimentoTransportMeansService.delete(
db, transport_mean_id, pedimento_id, tenant_id, company_id
)
if not success:
raise HTTPException(status_code=404, detail="Transport mean not found")

Some files were not shown because too many files have changed in this diff Show More