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

View File

@@ -5,6 +5,7 @@ Revises:
Create Date: 2025-10-19 18:23:39.613953 Create Date: 2025-10-19 18:23:39.613953
""" """
from typing import Sequence, Union from typing import Sequence, Union
from alembic import op from alembic import op
@@ -12,7 +13,7 @@ import sqlalchemy as sa
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision: str = '531bf8cdae06' revision: str = "531bf8cdae06"
down_revision: Union[str, Sequence[str], None] = None down_revision: Union[str, Sequence[str], None] = None
branch_labels: Union[str, Sequence[str], None] = None branch_labels: Union[str, Sequence[str], None] = None
depends_on: 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: def upgrade() -> None:
"""Upgrade schema.""" """Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ### # ### commands auto generated by Alembic - please adjust! ###
op.create_table('containers', op.create_table(
sa.Column('key', sa.String(length=3), nullable=False), "containers",
sa.Column('description', sa.String(length=500), nullable=False), sa.Column("key", sa.String(length=3), nullable=False),
sa.PrimaryKeyConstraint('key', name='containers_pkey'), sa.Column("description", sa.String(length=500), nullable=False),
schema='public' sa.PrimaryKeyConstraint("key", name="containers_pkey"),
schema="public",
) )
op.create_table('countries', op.create_table(
sa.Column('m3_key', sa.String(length=3), nullable=False), "countries",
sa.Column('mex_key', sa.String(length=2), nullable=False), sa.Column("m3_key", sa.String(length=3), nullable=False),
sa.Column('ame_key', sa.String(length=2), nullable=False), sa.Column("mex_key", sa.String(length=2), nullable=False),
sa.Column('description_es', sa.String(length=50), nullable=False), sa.Column("ame_key", sa.String(length=2), nullable=False),
sa.Column('description_en', sa.String(length=50), nullable=False), sa.Column("description_es", sa.String(length=50), nullable=False),
sa.PrimaryKeyConstraint('m3_key', name='countries_pkey'), sa.Column("description_en", sa.String(length=50), nullable=False),
schema='public' sa.PrimaryKeyConstraint("m3_key", name="countries_pkey"),
schema="public",
) )
op.create_index('ak_country_ame', 'countries', ['ame_key'], unique=True, schema='public') op.create_index(
op.create_table('currency_types', "ak_country_ame", "countries", ["ame_key"], unique=True, schema="public"
sa.Column('code', sa.String(length=3), nullable=False),
sa.Column('currency_name', sa.String(length=15), nullable=False),
sa.Column('country_description', sa.String(length=50), nullable=False),
sa.PrimaryKeyConstraint('code', name='currency_types_pkey'),
schema='public'
) )
op.create_table('customs_sections', op.create_table(
sa.Column('customs_code', sa.String(length=3), nullable=False), "currency_types",
sa.Column('section_name', sa.String(length=255), nullable=False), sa.Column("code", sa.String(length=3), nullable=False),
sa.PrimaryKeyConstraint('customs_code', name='customs_code_pkey'), sa.Column("currency_name", sa.String(length=15), nullable=False),
schema='public' sa.Column("country_description", sa.String(length=50), nullable=False),
sa.PrimaryKeyConstraint("code", name="currency_types_pkey"),
schema="public",
) )
op.create_table('customs_warehouses', op.create_table(
sa.Column('key', sa.String(length=3), nullable=False), "customs_sections",
sa.Column('customs', sa.String(length=100), nullable=False), sa.Column("customs_code", sa.String(length=3), nullable=False),
sa.Column('fiscalized_warehouse', sa.String(length=1000), nullable=False), sa.Column("section_name", sa.String(length=255), nullable=False),
sa.PrimaryKeyConstraint('key', 'customs', name='pk_customs_warehouse'), sa.PrimaryKeyConstraint("customs_code", name="customs_code_pkey"),
schema='public' schema="public",
) )
op.create_table('incoterms', op.create_table(
sa.Column('code', sa.String(length=5), nullable=False), "customs_warehouses",
sa.Column('description_es', sa.String(length=256), nullable=False), sa.Column("key", sa.String(length=3), nullable=False),
sa.Column('description_en', sa.String(length=256), nullable=False), sa.Column("customs", sa.String(length=100), nullable=False),
sa.PrimaryKeyConstraint('code', name='incoterms_pkey'), sa.Column("fiscalized_warehouse", sa.String(length=1000), nullable=False),
schema='public' sa.PrimaryKeyConstraint("key", "customs", name="pk_customs_warehouse"),
schema="public",
) )
op.create_table('invoice_types', op.create_table(
sa.Column('key', sa.String(length=5), nullable=False), "incoterms",
sa.Column('description', sa.String(length=50), nullable=False), sa.Column("code", sa.String(length=5), nullable=False),
sa.Column('note', sa.String(length=500), nullable=False), sa.Column("description_es", sa.String(length=256), nullable=False),
sa.Column('type', sa.String(length=15), nullable=False), sa.Column("description_en", sa.String(length=256), nullable=False),
sa.PrimaryKeyConstraint('key', name='invoice_types_pkey'), sa.PrimaryKeyConstraint("code", name="incoterms_pkey"),
schema='public' schema="public",
) )
op.create_table('material_types', op.create_table(
sa.Column('key', sa.String(length=10), nullable=False), "invoice_types",
sa.Column('type', sa.String(length=15), nullable=False), sa.Column("key", sa.String(length=5), nullable=False),
sa.Column('description', sa.String(length=256), nullable=False), sa.Column("description", sa.String(length=50), nullable=False),
sa.PrimaryKeyConstraint('key', name='material_types_pkey'), sa.Column("note", sa.String(length=500), nullable=False),
schema='public' sa.Column("type", sa.String(length=15), nullable=False),
sa.PrimaryKeyConstraint("key", name="invoice_types_pkey"),
schema="public",
) )
op.create_table('payment_methods', op.create_table(
sa.Column('key', sa.String(length=2), nullable=False), "material_types",
sa.Column('description', sa.String(length=100), nullable=False), sa.Column("key", sa.String(length=10), nullable=False),
sa.PrimaryKeyConstraint('key', name='payment_methods_pkey'), sa.Column("type", sa.String(length=15), nullable=False),
schema='public' sa.Column("description", sa.String(length=256), nullable=False),
sa.PrimaryKeyConstraint("key", name="material_types_pkey"),
schema="public",
) )
op.create_table('pedimento_codes', op.create_table(
sa.Column('code', sa.String(length=3), nullable=False), "payment_methods",
sa.Column('description', sa.String(length=250), nullable=False), sa.Column("key", sa.String(length=2), nullable=False),
sa.PrimaryKeyConstraint('code', name='pedimento_codes_pkey'), sa.Column("description", sa.String(length=100), nullable=False),
schema='public' sa.PrimaryKeyConstraint("key", name="payment_methods_pkey"),
schema="public",
) )
op.create_table('pedimento_regimens', op.create_table(
sa.Column('code', sa.String(length=3), nullable=False), "pedimento_codes",
sa.Column('description', sa.String(length=100), nullable=False), sa.Column("code", sa.String(length=3), nullable=False),
sa.PrimaryKeyConstraint('code', name='pedimento_regimens_pkey'), sa.Column("description", sa.String(length=250), nullable=False),
schema='public' sa.PrimaryKeyConstraint("code", name="pedimento_codes_pkey"),
schema="public",
) )
op.create_table('sectors', op.create_table(
sa.Column('key', sa.String(length=8), nullable=False), "pedimento_regimens",
sa.Column('description', sa.String(length=150), nullable=False), sa.Column("code", sa.String(length=3), nullable=False),
sa.Column('authorized', sa.SmallInteger(), nullable=False), sa.Column("description", sa.String(length=100), nullable=False),
sa.PrimaryKeyConstraint('key', name='sectors_pkey'), sa.PrimaryKeyConstraint("code", name="pedimento_regimens_pkey"),
schema='public' schema="public",
) )
op.create_table('states', op.create_table(
sa.Column('m3_key', sa.String(length=3), nullable=False), "sectors",
sa.Column('description', sa.String(length=50), nullable=False), sa.Column("key", sa.String(length=8), nullable=False),
sa.Column('mex_key', sa.String(length=3), nullable=True), sa.Column("description", sa.String(length=150), nullable=False),
sa.Column('ame_key', sa.String(length=2), nullable=True), sa.Column("authorized", sa.SmallInteger(), nullable=False),
sa.PrimaryKeyConstraint('m3_key', 'description', name='states_pkey'), sa.PrimaryKeyConstraint("key", name="sectors_pkey"),
schema='public' schema="public",
) )
op.create_table('transport_modes', op.create_table(
sa.Column('key', sa.String(length=3), nullable=False), "states",
sa.Column('name', sa.String(length=30), nullable=False), sa.Column("m3_key", sa.String(length=3), nullable=False),
sa.PrimaryKeyConstraint('key', name='transport_modes_pkey'), sa.Column("description", sa.String(length=50), nullable=False),
schema='public' 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', op.create_table(
sa.Column('transport_code', sa.String(length=2), nullable=False), "transport_modes",
sa.Column('description', sa.String(length=100), nullable=False), sa.Column("key", sa.String(length=3), nullable=False),
sa.PrimaryKeyConstraint('transport_code', name='transport_types_pkey'), sa.Column("name", sa.String(length=30), nullable=False),
schema='public' sa.PrimaryKeyConstraint("key", name="transport_modes_pkey"),
schema="public",
) )
op.create_table('valuation_methods', op.create_table(
sa.Column('key', sa.String(length=2), nullable=False), "transport_types",
sa.Column('description', sa.String(length=200), nullable=False), sa.Column("transport_code", sa.String(length=2), nullable=False),
sa.PrimaryKeyConstraint('key', name='valuation_methods_pkey'), sa.Column("description", sa.String(length=100), nullable=False),
schema='public' sa.PrimaryKeyConstraint("transport_code", name="transport_types_pkey"),
schema="public",
) )
op.create_table('code_pedimento_regimens', op.create_table(
sa.Column('id', sa.Integer(), nullable=False), "valuation_methods",
sa.Column('pedimento_code', sa.String(length=3), nullable=False), sa.Column("key", sa.String(length=2), nullable=False),
sa.Column('regimen_code', sa.String(length=3), nullable=False), sa.Column("description", sa.String(length=200), nullable=False),
sa.Column('type_code', sa.String(length=1), nullable=True), sa.PrimaryKeyConstraint("key", name="valuation_methods_pkey"),
sa.ForeignKeyConstraint(['pedimento_code'], ['public.pedimento_codes.code'], name='fk_codeped'), schema="public",
sa.ForeignKeyConstraint(['regimen_code'], ['public.pedimento_regimens.code'], name='fk_regimenped'), )
sa.PrimaryKeyConstraint('id', name='clave_pedimento_regimens_pkey'), op.create_table(
schema='public' "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 ### # ### end Alembic commands ###
@@ -146,22 +170,22 @@ def upgrade() -> None:
def downgrade() -> None: def downgrade() -> None:
"""Downgrade schema.""" """Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ### # ### commands auto generated by Alembic - please adjust! ###
op.drop_table('code_pedimento_regimens', schema='public') op.drop_table("code_pedimento_regimens", schema="public")
op.drop_table('valuation_methods', schema='public') op.drop_table("valuation_methods", schema="public")
op.drop_table('transport_types', schema='public') op.drop_table("transport_types", schema="public")
op.drop_table('transport_modes', schema='public') op.drop_table("transport_modes", schema="public")
op.drop_table('states', schema='public') op.drop_table("states", schema="public")
op.drop_table('sectors', schema='public') op.drop_table("sectors", schema="public")
op.drop_table('pedimento_regimens', schema='public') op.drop_table("pedimento_regimens", schema="public")
op.drop_table('pedimento_codes', schema='public') op.drop_table("pedimento_codes", schema="public")
op.drop_table('payment_methods', schema='public') op.drop_table("payment_methods", schema="public")
op.drop_table('material_types', schema='public') op.drop_table("material_types", schema="public")
op.drop_table('invoice_types', schema='public') op.drop_table("invoice_types", schema="public")
op.drop_table('incoterms', schema='public') op.drop_table("incoterms", schema="public")
op.drop_table('customs_warehouses', schema='public') op.drop_table("customs_warehouses", schema="public")
op.drop_table('customs_sections', schema='public') op.drop_table("customs_sections", schema="public")
op.drop_table('currency_types', schema='public') op.drop_table("currency_types", schema="public")
op.drop_index('ak_country_ame', table_name='countries', schema='public') op.drop_index("ak_country_ame", table_name="countries", schema="public")
op.drop_table('countries', schema='public') op.drop_table("countries", schema="public")
op.drop_table('containers', schema='public') op.drop_table("containers", schema="public")
# ### end Alembic commands ### # ### end Alembic commands ###

View File

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

View File

@@ -1,18 +1,30 @@
from decimal import Decimal 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 import Mapped, mapped_column
from sqlalchemy.orm.base import Mapped from sqlalchemy.orm.base import Mapped
from core.database import Base from core.database import Base
class QClasses(Base): class QClasses(Base):
__tablename__ = 'q_classes' #QClases __tablename__ = "q_classes" # QClases
__table_args__ = ( __table_args__ = (
PrimaryKeyConstraint('id', name='qclases_pk'), PrimaryKeyConstraint("id", name="qclases_pk"),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_qclasses_tenants'), ForeignKeyConstraint(
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_qclasses_company'), ["tenant_id"], ["a76.tenants.id"], name="fk_qclasses_tenants"
ForeignKeyConstraint(['class_id'], ['classes.id'], name='fk_qclasses_classes'), ),
{'schema': 'a24'} 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) 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) tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
company_id: Mapped[int] = mapped_column(Integer, nullable=True, 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_code: Mapped[str] = mapped_column(String(10)) # FRACCIONIMPO
import_tariff_type: Mapped[str] = mapped_column(String(6)) #TIPOFRACIMPO import_tariff_type: Mapped[str] = mapped_column(String(6)) # TIPOFRACIMPO
export_tariff_code: Mapped[str] = mapped_column(String(10)) #FRACCIONEXPO export_tariff_code: Mapped[str] = mapped_column(String(10)) # FRACCIONEXPO
export_tariff_type: Mapped[str] = mapped_column(String(6)) #TIPOFRACEXPO export_tariff_type: Mapped[str] = mapped_column(String(6)) # TIPOFRACEXPO
depreciation_rate: Mapped[Decimal] = mapped_column(Numeric(5, 2)) #TASADEPRECIA depreciation_rate: Mapped[Decimal] = mapped_column(Numeric(5, 2)) # TASADEPRECIA
fda_code: Mapped[str] = mapped_column(String(20)) #FDA fda_code: Mapped[str] = mapped_column(String(20)) # FDA
eccn_code: Mapped[str] = mapped_column(String(20)) #ECCN eccn_code: Mapped[str] = mapped_column(String(20)) # ECCN
class_enabled: Mapped[bool] = mapped_column(Boolean) #HABILITADESHABILITACLASE 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 import Mapped, mapped_column
from sqlalchemy.orm.base import Mapped from sqlalchemy.orm.base import Mapped
from core.database import Base from core.database import Base
class SClasses(Base): class SClasses(Base):
__tablename__ = 's_classes' #SClases __tablename__ = "s_classes" # SClases
__table_args__ = ( __table_args__ = (
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_sclasses_tenants'), ForeignKeyConstraint(
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_sclasses_company'), ["tenant_id"], ["a76.tenants.id"], name="fk_sclasses_tenants"
ForeignKeyConstraint(['class_id'], ['a76.clases.class_id'], name='fk_sclasses_classes'), ),
PrimaryKeyConstraint('id', name='sclases_pk'), ForeignKeyConstraint(
{'schema': 'a24'} ["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) 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) tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
company_id: Mapped[int] = mapped_column(Integer, nullable=True, 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 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) us_tariff_code: Mapped[str] = mapped_column(String(19)) # Fracción americana (USA)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,17 @@
""" """
Modelos ORM para gestión de clases SCAII y SCAF Modelos ORM para gestión de clases SCAII y SCAF
""" """
from typing import TYPE_CHECKING, Optional 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 sqlalchemy.orm import Mapped, mapped_column, relationship
from core.database import Base 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 Modelo para la tabla GClases - Información de clases en sistemas SCAII y SCAF
""" """
__tablename__ = "classes" __tablename__ = "classes"
__table_args__ = ( __table_args__ = (
PrimaryKeyConstraint('id', name='classes_pkey'), PrimaryKeyConstraint("id", name="classes_pkey"),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_classes_tenant'), ForeignKeyConstraint(
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_classes_company'), ["tenant_id"], ["a76.tenants.id"], name="fk_classes_tenant"
ForeignKeyConstraint(['client_id'], ['a76.client_provider.id'], name='fk_classes_client'), ),
ForeignKeyConstraint(['material_key'], ['public.material_types.key'], name='fk_classes_material_type'), ForeignKeyConstraint(
UniqueConstraint('tenant_id', 'company_id', 'class_code', name='uq_classes_client_id_class_code'), ["company_id"], ["a76.company.id"], name="fk_classes_company"
{"schema": "a76"} ),
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) id: Mapped[int] = mapped_column(Integer, primary_key=True)
@@ -32,37 +57,45 @@ class Class(Base):
client_id: Mapped[int] = mapped_column(Integer) client_id: Mapped[int] = mapped_column(Integer)
# Unique constraint compuesta # Unique constraint compuesta
class_code: Mapped[str] = mapped_column(String(8)) #CLASE class_code: Mapped[str] = mapped_column(String(8)) # CLASE
# Basic information # Basic information
description_es: Mapped[Optional[str]] = mapped_column(String(500)) #DESCRIPCIONE description_es: Mapped[Optional[str]] = mapped_column(String(500)) # DESCRIPCIONE
description_en: Mapped[Optional[str]] = mapped_column(String(500)) #DESCRIPCIONI description_en: Mapped[Optional[str]] = mapped_column(String(500)) # DESCRIPCIONI
# Material and measurement # Material and measurement
material_key: Mapped[Optional[str]] = mapped_column(String(10), ForeignKey('public.material_types.key')) # CLAVEMAT - homologated from TIPOMAT/TIPOMATEQUIPO material_key: Mapped[Optional[str]] = mapped_column(
unit_of_measure: Mapped[Optional[str]] = mapped_column(String(5)) # UNIMED - homologated from UNIMEDIDA 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 # Tariff fractions
fraction: Mapped[Optional[str]] = mapped_column(String(10)) # FRACCION 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 # Additional classification
sub_key: Mapped[Optional[str]] = mapped_column(String(5)) # CLAVESUB sub_key: Mapped[Optional[str]] = mapped_column(String(5)) # CLAVESUB
physical_review: Mapped[Optional[int]] = mapped_column(SmallInteger) # REVFISICA 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 # 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 # Inverse relationship with GParts that have this class
parts: Mapped[list["Part"]] = relationship( parts: Mapped[list["Part"]] = relationship(
primaryjoin="and_(Class.client_id == Part.client_id, Class.class_code == Part.part_class)", primaryjoin="and_(Class.client_id == Part.client_id, Class.class_code == Part.part_class)",
foreign_keys="[Part.client_id, Part.part_class]", foreign_keys="[Part.client_id, Part.part_class]",
viewonly=True, viewonly=True,
back_populates="part_class_info" back_populates="part_class_info",
) )
def __repr__(self) -> str: def __repr__(self) -> str:
return f"<Class(client_id={self.client_id}, class_code='{self.class_code}', description='{self.description_es}')>" 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 Endpoints API para gestión de clases SCAII y SCAF
""" """
from fastapi import APIRouter, Depends, HTTPException, Query, status from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from typing import List, Optional from typing import List, Optional
@@ -14,23 +15,28 @@ from .dto import (
ClassResponseDTO, ClassResponseDTO,
ClassBasicDTO, ClassBasicDTO,
ClassListDTO, ClassListDTO,
ClassSearchDTO ClassSearchDTO,
) )
router = APIRouter(prefix="/classes", tags=["Classes"]) router = APIRouter(prefix="/classes", tags=["Classes"])
@router.get("/", response_model=ClassListDTO) @router.get("/", response_model=ClassListDTO)
async def list_classes( async def list_classes(
skip: int = Query(0, ge=0, description="Number of records to skip"), 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"), client_id: Optional[int] = Query(None, description="Filter by client key"),
class_code: Optional[str] = Query(None, description="Search by class code"), class_code: Optional[str] = Query(None, description="Search by class code"),
description: Optional[str] = Query(None, description="Search in descriptions"), description: Optional[str] = Query(None, description="Search in descriptions"),
material_key: Optional[str] = Query(None, description="Filter by material key"), material_key: Optional[str] = Query(None, description="Filter by material key"),
fraction: Optional[str] = Query(None, description="Filter by tariff fraction"), 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), 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 List classes with optional filters and pagination
@@ -49,7 +55,7 @@ async def list_classes(
description=description, description=description,
material_key=material_key, material_key=material_key,
fraction=fraction, fraction=fraction,
physical_review=physical_review physical_review=physical_review,
) )
return service.list_classes(skip, limit, search_params) return service.list_classes(skip, limit, search_params)
@@ -60,7 +66,7 @@ async def get_classes_by_client(
skip: int = Query(0, ge=0), skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000), limit: int = Query(100, ge=1, le=1000),
db: Session = Depends(get_core_db), 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 Get all classes for a specific client
@@ -80,7 +86,7 @@ async def get_classes_by_client(
async def search_by_fraction( async def search_by_fraction(
fraction: str, fraction: str,
db: Session = Depends(get_core_db), 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 Search classes by tariff fraction
@@ -93,7 +99,7 @@ async def search_by_fraction(
async def search_by_material( async def search_by_material(
material_key: str, material_key: str,
db: Session = Depends(get_core_db), 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 Search classes by material key
@@ -102,11 +108,13 @@ async def search_by_material(
return service.search_by_material(material_key) 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( async def get_classes_by_unit_measure(
unit_of_measure: str, unit_of_measure: str,
db: Session = Depends(get_core_db), 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 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) 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( async def get_classes_by_physical_review(
physical_review: int, physical_review: int,
db: Session = Depends(get_core_db), 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 Get classes by physical review indicator
@@ -130,8 +140,7 @@ async def get_classes_by_physical_review(
@router.get("/statistics", response_model=dict) @router.get("/statistics", response_model=dict)
async def get_classes_statistics( async def get_classes_statistics(
db: Session = Depends(get_core_db), db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user)
): ):
""" """
Get basic classes statistics Get basic classes statistics
@@ -145,7 +154,7 @@ async def get_class(
client_id: int, client_id: int,
class_code: str, class_code: str,
db: Session = Depends(get_core_db), 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) Get class by composite key (client_id + class_code)
@@ -155,15 +164,16 @@ async def get_class(
if not class_obj: if not class_obj:
raise HTTPException( raise HTTPException(
status_code=404, 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 return class_obj
@router.post("/", response_model=ClassResponseDTO, status_code=status.HTTP_201_CREATED) @router.post("/", response_model=ClassResponseDTO, status_code=status.HTTP_201_CREATED)
async def create_class( async def create_class(
class_data: ClassCreateDTO, class_data: ClassCreateDTO,
db: Session = Depends(get_core_db), 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 Create a new class in the system
@@ -171,13 +181,14 @@ async def create_class(
service = ClassService(db) service = ClassService(db)
return service.create_class(class_data) return service.create_class(class_data)
@router.put("/{client_id}/{class_code}", response_model=ClassResponseDTO) @router.put("/{client_id}/{class_code}", response_model=ClassResponseDTO)
async def update_class( async def update_class(
client_id: int, client_id: int,
class_code: str, class_code: str,
class_data: ClassUpdateDTO, class_data: ClassUpdateDTO,
db: Session = Depends(get_core_db), db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user) current_user: dict = Depends(get_current_user),
): ):
""" """
Update class information Update class information
@@ -187,7 +198,7 @@ async def update_class(
if not class_obj: if not class_obj:
raise HTTPException( raise HTTPException(
status_code=404, 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 return class_obj
@@ -197,7 +208,7 @@ async def delete_class(
client_id: int, client_id: int,
class_code: str, class_code: str,
db: Session = Depends(get_core_db), 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 Delete class from the system
@@ -208,7 +219,7 @@ async def delete_class(
if not service.delete_class(client_id, class_code): if not service.delete_class(client_id, class_code):
raise HTTPException( raise HTTPException(
status_code=404, 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, client_id: int,
class_code: str, class_code: str,
db: Session = Depends(get_core_db), 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 Get basic information for a class
@@ -228,7 +239,7 @@ async def get_class_basic_info(
if not class_obj: if not class_obj:
raise HTTPException( raise HTTPException(
status_code=404, 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( return ClassBasicDTO(
@@ -237,7 +248,7 @@ async def get_class_basic_info(
description_spanish=class_obj.description_spanish, description_spanish=class_obj.description_spanish,
description_english=class_obj.description_english, description_english=class_obj.description_english,
material_key=class_obj.material_key, 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, client_id: int,
class_code: str, class_code: str,
db: Session = Depends(get_core_db), 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.) Get tariff information for a class (fractions, IVA exempt, etc.)
@@ -256,7 +267,7 @@ async def get_class_tariff_info(
if not class_obj: if not class_obj:
raise HTTPException( raise HTTPException(
status_code=404, 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 { return {
@@ -266,7 +277,5 @@ async def get_class_tariff_info(
"us_fraction": class_obj.us_fraction, "us_fraction": class_obj.us_fraction,
"iva_exempt_fraction": class_obj.iva_exempt_fraction, "iva_exempt_fraction": class_obj.iva_exempt_fraction,
"sub_key": class_obj.sub_key, "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 Capa de servicio para lógica de negocio de clases SCAII y SCAF
""" """
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from sqlalchemy.exc import IntegrityError from sqlalchemy.exc import IntegrityError
from sqlalchemy import or_, and_, func from sqlalchemy import or_, and_, func
@@ -15,7 +16,7 @@ from .dto import (
ClassResponseDTO, ClassResponseDTO,
ClassBasicDTO, ClassBasicDTO,
ClassListDTO, ClassListDTO,
ClassSearchDTO ClassSearchDTO,
) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -42,17 +43,21 @@ class ClassService:
""" """
try: try:
# Verificar que no exista la clase # Verificar que no exista la clase
existing = self.db.query(Class).filter( existing = (
and_( self.db.query(Class)
Class.client_id == class_data.client_id, .filter(
Class.class_code == class_data.class_code and_(
Class.client_id == class_data.client_id,
Class.class_code == class_data.class_code,
)
) )
).first() .first()
)
if existing: if existing:
raise HTTPException( raise HTTPException(
status_code=400, 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 # Crear clase
@@ -67,7 +72,7 @@ class ClassService:
us_fraction=class_data.us_fraction, us_fraction=class_data.us_fraction,
sub_key=class_data.sub_key, sub_key=class_data.sub_key,
physical_review=class_data.physical_review, 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) self.db.add(db_class)
@@ -81,7 +86,10 @@ class ClassService:
except IntegrityError as e: except IntegrityError as e:
self.db.rollback() self.db.rollback()
logger.error(f"IntegrityError creating class: {str(e)}") 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: except HTTPException:
raise raise
except Exception as e: except Exception as e:
@@ -100,12 +108,11 @@ class ClassService:
Returns: Returns:
ClassResponseDTO o None si no existe ClassResponseDTO o None si no existe
""" """
class_obj = self.db.query(Class).filter( class_obj = (
and_( self.db.query(Class)
Class.client_id == client_id, .filter(and_(Class.client_id == client_id, Class.class_code == class_code))
Class.class_code == class_code .first()
) )
).first()
if not class_obj: if not class_obj:
return None return None
@@ -115,7 +122,7 @@ class ClassService:
self, self,
skip: int = 0, skip: int = 0,
limit: int = 100, limit: int = 100,
search_params: Optional[ClassSearchDTO] = None search_params: Optional[ClassSearchDTO] = None,
) -> ClassListDTO: ) -> ClassListDTO:
""" """
Lista clases con filtros Lista clases con filtros
@@ -136,25 +143,33 @@ class ClassService:
query = query.filter(Class.client_id == search_params.client_id) query = query.filter(Class.client_id == search_params.client_id)
if search_params.class_code: 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: if search_params.description:
description_pattern = f"%{search_params.description}%" description_pattern = f"%{search_params.description}%"
query = query.filter( query = query.filter(
or_( or_(
Class.description_spanish.ilike(description_pattern), Class.description_spanish.ilike(description_pattern),
Class.description_english.ilike(description_pattern) Class.description_english.ilike(description_pattern),
) )
) )
if search_params.material_key: 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: 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: 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 # Contar total
total = query.count() total = query.count()
@@ -169,10 +184,12 @@ class ClassService:
classes=class_dtos, classes=class_dtos,
total=total, total=total,
page=(skip // limit) + 1 if limit > 0 else 1, 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 Actualiza una clase
@@ -184,12 +201,11 @@ class ClassService:
Returns: Returns:
ClassResponseDTO actualizado o None si no existe ClassResponseDTO actualizado o None si no existe
""" """
class_obj = self.db.query(Class).filter( class_obj = (
and_( self.db.query(Class)
Class.client_id == client_id, .filter(and_(Class.client_id == client_id, Class.class_code == class_code))
Class.class_code == class_code .first()
) )
).first()
if not class_obj: if not class_obj:
return None return None
@@ -222,12 +238,11 @@ class ClassService:
Returns: Returns:
True si se eliminó, False si no existe True si se eliminó, False si no existe
""" """
class_obj = self.db.query(Class).filter( class_obj = (
and_( self.db.query(Class)
Class.client_id == client_id, .filter(and_(Class.client_id == client_id, Class.class_code == class_code))
Class.class_code == class_code .first()
) )
).first()
if not class_obj: if not class_obj:
return False return False
@@ -244,22 +259,40 @@ class ClassService:
def search_by_fraction(self, fraction: str) -> List[ClassBasicDTO]: def search_by_fraction(self, fraction: str) -> List[ClassBasicDTO]:
"""Busca clases por fracción arancelaria""" """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] 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""" """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] return [ClassBasicDTO.model_validate(class_obj) for class_obj in classes]
def search_by_material(self, material_key: str) -> List[ClassBasicDTO]: def search_by_material(self, material_key: str) -> List[ClassBasicDTO]:
"""Busca clases por clave de material""" """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] 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""" """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] return [ClassBasicDTO.model_validate(class_obj) for class_obj in classes]
def get_classes_statistics(self) -> dict: def get_classes_statistics(self) -> dict:
@@ -277,18 +310,21 @@ class ClassService:
# Contar clases con fracciones # Contar clases con fracciones
with_fraction = self.db.query(Class).filter(Class.fraction.isnot(None)).count() 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 { return {
"total_classes": total_classes, "total_classes": total_classes,
"clients_with_classes": clients_count, "clients_with_classes": clients_count,
"classes_with_fraction": with_fraction, "classes_with_fraction": with_fraction,
"classes_with_us_fraction": with_us_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]: def get_classes_by_unit_measure(self, unit_of_measure: str) -> List[ClassBasicDTO]:
"""Obtiene clases por unidad de medida""" """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] return [ClassBasicDTO.model_validate(class_obj) for class_obj in classes]

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
""" """
Endpoints API para gestión de clientes y proveedores Endpoints API para gestión de clientes y proveedores
""" """
from fastapi import APIRouter, Depends, HTTPException, Query, status from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from typing import List, Optional from typing import List, Optional
@@ -13,17 +14,19 @@ from .dto import (
ClientProviderUpdateDTO, ClientProviderUpdateDTO,
ClientProviderResponseDTO, ClientProviderResponseDTO,
ClientProviderBasicDTO, ClientProviderBasicDTO,
ClientProviderListDTO ClientProviderListDTO,
) )
router = APIRouter(prefix="/clients-providers") 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( async def create_client_provider(
client_data: ClientProviderCreateDTO, client_data: ClientProviderCreateDTO,
db: Session = Depends(get_core_db), 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 Create a new client or provider in the system
@@ -46,12 +49,16 @@ async def create_client_provider(
@router.get("/", response_model=ClientProviderListDTO) @router.get("/", response_model=ClientProviderListDTO)
async def list_clients_providers( async def list_clients_providers(
skip: int = Query(0, ge=0, description="Number of records to skip"), 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"), 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"), enabled_only: bool = Query(False, description="Show only enabled records"),
db: Session = Depends(get_core_db), 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 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") raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found")
service = ClientProviderService(db) 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]) @router.get("/clients", response_model=List[ClientProviderBasicDTO])
@@ -72,7 +81,7 @@ async def get_clients_only(
skip: int = Query(0, ge=0), skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000), limit: int = Query(100, ge=1, le=1000),
db: Session = Depends(get_core_db), 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') Get only clients (client_or_provider = 'C')
@@ -93,7 +102,7 @@ async def get_providers_only(
skip: int = Query(0, ge=0), skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000), limit: int = Query(100, ge=1, le=1000),
db: Session = Depends(get_core_db), 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') Get only providers (client_or_provider = 'P')
@@ -113,7 +122,7 @@ async def get_providers_only(
async def search_by_rfc( async def search_by_rfc(
rfc: str, rfc: str,
db: Session = Depends(get_core_db), 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 Search clients/providers by RFC
@@ -133,7 +142,7 @@ async def search_by_rfc(
async def get_client_provider( async def get_client_provider(
client_id: str, client_id: str,
db: Session = Depends(get_core_db), 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 Get client/provider by ID with all related information
@@ -148,7 +157,9 @@ async def get_client_provider(
service = ClientProviderService(db) service = ClientProviderService(db)
client = service.get_client_provider(client_id) client = service.get_client_provider(client_id)
if not client: 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 return client
@@ -157,7 +168,7 @@ async def update_client_provider(
client_id: str, client_id: str,
client_data: ClientProviderUpdateDTO, client_data: ClientProviderUpdateDTO,
db: Session = Depends(get_core_db), db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user) current_user: dict = Depends(get_current_user),
): ):
""" """
Update client/provider information Update client/provider information
@@ -172,7 +183,9 @@ async def update_client_provider(
service = ClientProviderService(db) service = ClientProviderService(db)
client = service.update_client_provider(client_id, client_data) client = service.update_client_provider(client_id, client_data)
if not client: 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 return client
@@ -180,7 +193,7 @@ async def update_client_provider(
async def delete_client_provider( async def delete_client_provider(
client_id: str, client_id: str,
db: Session = Depends(get_core_db), 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 Delete client/provider from the system
@@ -196,14 +209,16 @@ async def delete_client_provider(
service = ClientProviderService(db) service = ClientProviderService(db)
if not service.delete_client_provider(client_id): 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) @router.patch("/{client_id}/toggle-status", response_model=ClientProviderResponseDTO)
async def toggle_client_provider_status( async def toggle_client_provider_status(
client_id: str, client_id: str,
db: Session = Depends(get_core_db), 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 Toggle client/provider enabled/disabled status
@@ -218,7 +233,9 @@ async def toggle_client_provider_status(
service = ClientProviderService(db) service = ClientProviderService(db)
client = service.toggle_status(client_id) client = service.toggle_status(client_id)
if not client: 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 return client
@@ -227,7 +244,7 @@ async def toggle_client_provider_status(
async def get_client_provider_address( async def get_client_provider_address(
client_id: str, client_id: str,
db: Session = Depends(get_core_db), 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 Get only address information for a client/provider
@@ -242,19 +259,18 @@ async def get_client_provider_address(
service = ClientProviderService(db) service = ClientProviderService(db)
client = service.get_client_provider(client_id) client = service.get_client_provider(client_id)
if not client: 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 { return {"client_id": client.client_id, "address": client.address}
"client_id": client.client_id,
"address": client.address
}
@router.get("/{client_id}/programs", response_model=dict) @router.get("/{client_id}/programs", response_model=dict)
async def get_client_provider_programs( async def get_client_provider_programs(
client_id: str, client_id: str,
db: Session = Depends(get_core_db), 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 Get only programs information for a client/provider
@@ -269,19 +285,18 @@ async def get_client_provider_programs(
service = ClientProviderService(db) service = ClientProviderService(db)
client = service.get_client_provider(client_id) client = service.get_client_provider(client_id)
if not client: 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 { return {"client_id": client.client_id, "programs": client.programs}
"client_id": client.client_id,
"programs": client.programs
}
@router.get("/{client_id}/basic", response_model=ClientProviderBasicDTO) @router.get("/{client_id}/basic", response_model=ClientProviderBasicDTO)
async def get_client_provider_basic_info( async def get_client_provider_basic_info(
client_id: str, client_id: str,
db: Session = Depends(get_core_db), 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) 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) service = ClientProviderService(db)
client = service.get_client_provider(client_id) client = service.get_client_provider(client_id)
if not client: 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( return ClientProviderBasicDTO(
client_id=client.client_id, client_id=client.client_id,
@@ -304,6 +321,5 @@ async def get_client_provider_basic_info(
short_name=client.short_name, short_name=client.short_name,
rfc=client.rfc, rfc=client.rfc,
client_or_provider=client.client_or_provider, 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 Capa de servicio para lógica de negocio de clientes y proveedores
""" """
from sqlalchemy.orm import Session, joinedload from sqlalchemy.orm import Session, joinedload
from sqlalchemy.exc import IntegrityError from sqlalchemy.exc import IntegrityError
from sqlalchemy import or_, and_ from sqlalchemy import or_, and_
@@ -16,7 +17,7 @@ from .dto import (
ClientProviderBasicDTO, ClientProviderBasicDTO,
ClientProviderListDTO, ClientProviderListDTO,
ClientProviderAddressDTO, ClientProviderAddressDTO,
ClientProviderProgramsDTO ClientProviderProgramsDTO,
) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -28,7 +29,9 @@ class ClientProviderService:
def __init__(self, db: Session): def __init__(self, db: Session):
self.db = db 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 Crea un nuevo cliente/proveedor en el sistema
@@ -43,9 +46,16 @@ class ClientProviderService:
""" """
try: try:
# Verificar que no exista el cliente # 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: 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 # Crear cliente/proveedor principal
db_client = ClientProvider( db_client = ClientProvider(
@@ -64,7 +74,7 @@ class ClientProviderService:
position=client_data.position, position=client_data.position,
incoterm=client_data.incoterm, incoterm=client_data.incoterm,
is_national_provider=client_data.is_national_provider, 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) self.db.add(db_client)
@@ -74,7 +84,7 @@ class ClientProviderService:
if client_data.address: if client_data.address:
db_address = ClientProviderAddress( db_address = ClientProviderAddress(
client_id=client_data.client_id, 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) self.db.add(db_address)
@@ -82,29 +92,37 @@ class ClientProviderService:
if client_data.programs: if client_data.programs:
db_programs = ClientProviderPrograms( db_programs = ClientProviderPrograms(
client_id=client_data.client_id, 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.add(db_programs)
self.db.commit() self.db.commit()
self.db.refresh(db_client) 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) return self._get_client_with_relations(client_data.client_id)
except IntegrityError as e: except IntegrityError as e:
self.db.rollback() self.db.rollback()
logger.error(f"IntegrityError creating client/provider: {str(e)}") 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: except HTTPException:
raise raise
except Exception as e: except Exception as e:
self.db.rollback() self.db.rollback()
logger.error(f"Error creating client/provider: {str(e)}") 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 Obtiene un cliente/proveedor por ID
@@ -116,12 +134,18 @@ class ClientProviderService:
""" """
return self._get_client_with_relations(client_id) 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""" """Método privado para obtener cliente con relaciones"""
client = self.db.query(ClientProvider).options( client = (
joinedload(ClientProvider.address), self.db.query(ClientProvider)
joinedload(ClientProvider.programs) .options(
).filter(ClientProvider.client_id == client_id).first() joinedload(ClientProvider.address), joinedload(ClientProvider.programs)
)
.filter(ClientProvider.client_id == client_id)
.first()
)
if not client: if not client:
return None return None
@@ -133,7 +157,7 @@ class ClientProviderService:
limit: int = 100, limit: int = 100,
search: Optional[str] = None, search: Optional[str] = None,
client_or_provider: Optional[str] = None, client_or_provider: Optional[str] = None,
enabled_only: bool = False enabled_only: bool = False,
) -> ClientProviderListDTO: ) -> ClientProviderListDTO:
""" """
Lista clientes/proveedores con filtros Lista clientes/proveedores con filtros
@@ -158,12 +182,14 @@ class ClientProviderService:
ClientProvider.name.ilike(search_pattern), ClientProvider.name.ilike(search_pattern),
ClientProvider.short_name.ilike(search_pattern), ClientProvider.short_name.ilike(search_pattern),
ClientProvider.rfc.ilike(search_pattern), ClientProvider.rfc.ilike(search_pattern),
ClientProvider.client_id.ilike(search_pattern) ClientProvider.client_id.ilike(search_pattern),
) )
) )
if client_or_provider: 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: if enabled_only:
query = query.filter(ClientProvider.enabled_disabled == 1) query = query.filter(ClientProvider.enabled_disabled == 1)
@@ -175,16 +201,20 @@ class ClientProviderService:
clients = query.offset(skip).limit(limit).all() clients = query.offset(skip).limit(limit).all()
# Convertir a DTOs básicos # 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( return ClientProviderListDTO(
clients=client_dtos, clients=client_dtos,
total=total, total=total,
page=(skip // limit) + 1 if limit > 0 else 1, 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 Actualiza un cliente/proveedor
@@ -195,19 +225,29 @@ class ClientProviderService:
Returns: Returns:
ClientProviderResponseDTO actualizado o None si no existe 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: if not client:
return None return None
try: try:
# Actualizar campos del cliente principal # 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(): for field, value in update_data.items():
setattr(client, field, value) setattr(client, field, value)
# Actualizar dirección # Actualizar dirección
if client_data.address: 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: if address:
# Actualizar dirección existente # Actualizar dirección existente
address_data = client_data.address.model_dump(exclude_unset=True) address_data = client_data.address.model_dump(exclude_unset=True)
@@ -217,13 +257,17 @@ class ClientProviderService:
# Crear nueva dirección # Crear nueva dirección
address = ClientProviderAddress( address = ClientProviderAddress(
client_id=client_id, client_id=client_id,
**client_data.address.model_dump(exclude_unset=True) **client_data.address.model_dump(exclude_unset=True),
) )
self.db.add(address) self.db.add(address)
# Actualizar programas # Actualizar programas
if client_data.programs: 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: if programs:
# Actualizar programas existentes # Actualizar programas existentes
programs_data = client_data.programs.model_dump(exclude_unset=True) programs_data = client_data.programs.model_dump(exclude_unset=True)
@@ -233,7 +277,7 @@ class ClientProviderService:
# Crear nuevos programas # Crear nuevos programas
programs = ClientProviderPrograms( programs = ClientProviderPrograms(
client_id=client_id, client_id=client_id,
**client_data.programs.model_dump(exclude_unset=True) **client_data.programs.model_dump(exclude_unset=True),
) )
self.db.add(programs) self.db.add(programs)
@@ -245,7 +289,9 @@ class ClientProviderService:
except Exception as e: except Exception as e:
self.db.rollback() self.db.rollback()
logger.error(f"Error updating client/provider {client_id}: {str(e)}") 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: def delete_client_provider(self, client_id: str) -> bool:
""" """
@@ -257,7 +303,11 @@ class ClientProviderService:
Returns: Returns:
True si se eliminó, False si no existe 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: if not client:
return False return False
@@ -269,28 +319,48 @@ class ClientProviderService:
except Exception as e: except Exception as e:
self.db.rollback() self.db.rollback()
logger.error(f"Error deleting client/provider {client_id}: {str(e)}") 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)""" """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() clients = query.offset(skip).limit(limit).all()
return [ClientProviderBasicDTO.model_validate(client) for client in clients] 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)""" """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() 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]: def search_by_rfc(self, rfc: str) -> List[ClientProviderBasicDTO]:
"""Busca clientes/proveedores por RFC""" """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] return [ClientProviderBasicDTO.model_validate(client) for client in clients]
def toggle_status(self, client_id: str) -> Optional[ClientProviderResponseDTO]: def toggle_status(self, client_id: str) -> Optional[ClientProviderResponseDTO]:
"""Cambia el estado habilitado/deshabilitado""" """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: if not client:
return None return None
@@ -299,11 +369,11 @@ class ClientProviderService:
try: try:
self.db.commit() 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) return self._get_client_with_relations(client_id)
except Exception as e: except Exception as e:
self.db.rollback() self.db.rollback()
logger.error(f"Error toggling status for {client_id}: {str(e)}") logger.error(f"Error toggling status for {client_id}: {str(e)}")
raise HTTPException(status_code=500, detail="Error updating status") raise HTTPException(status_code=500, detail="Error updating status")

View File

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

View File

@@ -2,6 +2,7 @@
DTOs (Data Transfer Objects) para módulo de empresa DTOs (Data Transfer Objects) para módulo de empresa
Reemplaza schemas.py siguiendo enfoque DDD y estilo NestJS Reemplaza schemas.py siguiendo enfoque DDD y estilo NestJS
""" """
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from typing import Optional from typing import Optional
from datetime import datetime from datetime import datetime
@@ -9,47 +10,80 @@ from datetime import datetime
class CompanyCreateDTO(BaseModel): class CompanyCreateDTO(BaseModel):
"""DTO para crear una empresa""" """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") consecutive: bool = Field(default=True, description="Unique record control")
name: Optional[str] = Field(None, max_length=255, description="Company name") name: Optional[str] = Field(None, max_length=255, description="Company name")
rfc: Optional[str] = Field(None, max_length=30, description="Company RFC") 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 information
program: Optional[str] = Field(None, max_length=10, description="Program") 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: 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 # Identifiers
manufacturer_id: Optional[str] = Field(None, max_length=25, description="Manufacturer ID") manufacturer_id: Optional[str] = Field(
broker_company: Optional[str] = Field(None, max_length=10, description="Broker company") None, max_length=25, description="Manufacturer ID"
)
broker_company: Optional[str] = Field(
None, max_length=10, description="Broker company"
)
# Responsible person # Responsible person
responsible: Optional[str] = Field(None, max_length=80, description="Responsible person") responsible: Optional[str] = Field(
responsible_name: Optional[str] = Field(None, max_length=20, description="Responsible first name") None, max_length=80, description="Responsible person"
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_name: Optional[str] = Field(
responsible_rfc: Optional[str] = Field(None, max_length=30, description="Responsible RFC") None, max_length=20, description="Responsible first name"
position: Optional[str] = Field(None, max_length=30, description="Responsible position") )
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 # Configuration
logo: Optional[str] = Field(None, max_length=255, description="Company logo") logo: Optional[str] = Field(None, max_length=255, description="Company logo")
has_express_line: Optional[bool] = Field(None, description="Has express line") 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") previous_code: Optional[int] = Field(None, description="Previous code")
is_service_company: Optional[bool] = Field(None, description="Is service company") is_service_company: Optional[bool] = Field(None, description="Is service company")
# Client and subassembly # Client and subassembly
client_name: Optional[str] = Field(None, max_length=300, description="Client name") 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 # Additional information
curp: Optional[str] = Field(None, max_length=19, description="CURP") 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") 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") trusted_exporter_number: Optional[str] = Field(
prevalidator_key: Optional[str] = Field(None, max_length=20, description="Prevalidator key") 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") seventh_amendment: Optional[bool] = Field(None, description="Seventh amendment")
class Config: class Config:
@@ -58,45 +92,78 @@ class CompanyCreateDTO(BaseModel):
class CompanyUpdateDTO(BaseModel): class CompanyUpdateDTO(BaseModel):
"""DTO para actualizar una empresa""" """DTO para actualizar una empresa"""
name: Optional[str] = Field(None, max_length=255, description="Company name") name: Optional[str] = Field(None, max_length=255, description="Company name")
rfc: Optional[str] = Field(None, max_length=30, description="Company RFC") 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 information
program: Optional[str] = Field(None, max_length=10, description="Program") 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: 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 # Identifiers
manufacturer_id: Optional[str] = Field(None, max_length=25, description="Manufacturer ID") manufacturer_id: Optional[str] = Field(
broker_company: Optional[str] = Field(None, max_length=10, description="Broker company") None, max_length=25, description="Manufacturer ID"
)
broker_company: Optional[str] = Field(
None, max_length=10, description="Broker company"
)
# Responsible person # Responsible person
responsible: Optional[str] = Field(None, max_length=80, description="Responsible person") responsible: Optional[str] = Field(
responsible_name: Optional[str] = Field(None, max_length=20, description="Responsible first name") None, max_length=80, description="Responsible person"
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_name: Optional[str] = Field(
responsible_rfc: Optional[str] = Field(None, max_length=30, description="Responsible RFC") None, max_length=20, description="Responsible first name"
position: Optional[str] = Field(None, max_length=30, description="Responsible position") )
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 # Configuration
logo: Optional[str] = Field(None, max_length=255, description="Company logo") logo: Optional[str] = Field(None, max_length=255, description="Company logo")
has_express_line: Optional[bool] = Field(None, description="Has express line") 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") previous_code: Optional[int] = Field(None, description="Previous code")
is_service_company: Optional[bool] = Field(None, description="Is service company") is_service_company: Optional[bool] = Field(None, description="Is service company")
# Client and subassembly # Client and subassembly
client_name: Optional[str] = Field(None, max_length=300, description="Client name") 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 # Additional information
curp: Optional[str] = Field(None, max_length=19, description="CURP") 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") 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") trusted_exporter_number: Optional[str] = Field(
prevalidator_key: Optional[str] = Field(None, max_length=20, description="Prevalidator key") 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") seventh_amendment: Optional[bool] = Field(None, description="Seventh amendment")
class Config: class Config:
@@ -105,8 +172,9 @@ class CompanyUpdateDTO(BaseModel):
class CompanyResponseDTO(BaseModel): class CompanyResponseDTO(BaseModel):
"""DTO para respuesta de empresa""" """DTO para respuesta de empresa"""
id: str
consecutive: bool id: int
tenant_id: int
name: Optional[str] = None name: Optional[str] = None
rfc: Optional[str] = None rfc: Optional[str] = None
main_activity: Optional[str] = None main_activity: Optional[str] = None
@@ -154,4 +222,3 @@ class CompanyResponseDTO(BaseModel):
class Config: class Config:
from_attributes = True from_attributes = True

View File

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

View File

@@ -1,23 +1,26 @@
""" """
Endpoints API para gestión de empresa Endpoints API para gestión de empresa
""" """
from fastapi import APIRouter, Depends, HTTPException, status from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from typing import Optional from typing import Optional
from core.database import get_core_db 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 .service import CompanyService
from .dto import CompanyCreateDTO, CompanyUpdateDTO, CompanyResponseDTO from .dto import CompanyCreateDTO, CompanyUpdateDTO, CompanyResponseDTO
router = APIRouter(prefix="/company") 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( async def create_company(
company_data: CompanyCreateDTO, company_data: CompanyCreateDTO,
db: Session = Depends(get_core_db), 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 Create a new company in the system
@@ -30,8 +33,7 @@ async def create_company(
@router.get("/", response_model=Optional[CompanyResponseDTO]) @router.get("/", response_model=Optional[CompanyResponseDTO])
async def get_company( async def get_company(
db: Session = Depends(get_core_db), db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user)
): ):
""" """
Get the registered company information Get the registered company information
@@ -45,73 +47,48 @@ async def get_company(
return company return company
@router.get("/{company_id}", response_model=CompanyResponseDTO) @router.get("/my-companies", response_model=list[CompanyResponseDTO])
async def get_company_by_id( async def get_my_companies(
company_id: str,
db: Session = Depends(get_core_db), db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user) 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) service = CompanyService(db)
company = service.get_company_by_id(company_id) companies = service.get_companies_by_tenant(tenant_id)
if not company:
raise HTTPException(status_code=404, detail=f"Company with ID '{company_id}' not found")
return company
return companies
@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")
@router.get("/status/exists", response_model=dict) @router.get("/status/exists", response_model=dict)
async def check_company_exists( async def check_company_exists(
db: Session = Depends(get_core_db), db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user)
): ):
""" """
Check if a company is registered in the system Check if a company is registered in the system
""" """
service = CompanyService(db) service = CompanyService(db)
exists = service.exists_company() 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 # Specific endpoints for important fields
@router.get("/info/basic", response_model=dict) @router.get("/info/basic", response_model=dict)
async def get_company_basic_info( async def get_company_basic_info(
db: Session = Depends(get_core_db), db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user)
): ):
""" """
Get basic company information (name, RFC, main activity) Get basic company information (name, RFC, main activity)
@@ -125,14 +102,13 @@ async def get_company_basic_info(
"name": company.name, "name": company.name,
"rfc": company.rfc, "rfc": company.rfc,
"main_activity": company.main_activity, "main_activity": company.main_activity,
"logo": company.logo "logo": company.logo,
} }
@router.get("/info/responsible", response_model=dict) @router.get("/info/responsible", response_model=dict)
async def get_company_responsible_info( async def get_company_responsible_info(
db: Session = Depends(get_core_db), db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user)
): ):
""" """
Get company responsible person information Get company responsible person information
@@ -148,14 +124,13 @@ async def get_company_responsible_info(
"responsible_last_name": company.responsible_last_name, "responsible_last_name": company.responsible_last_name,
"responsible_mother_last_name": company.responsible_mother_last_name, "responsible_mother_last_name": company.responsible_mother_last_name,
"responsible_rfc": company.responsible_rfc, "responsible_rfc": company.responsible_rfc,
"position": company.position "position": company.position,
} }
@router.get("/info/program", response_model=dict) @router.get("/info/program", response_model=dict)
async def get_company_program_info( async def get_company_program_info(
db: Session = Depends(get_core_db), db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user)
): ):
""" """
Get company program information Get company program information
@@ -170,7 +145,61 @@ async def get_company_program_info(
"program_number": company.program_number, "program_number": company.program_number,
"prosec": company.prosec, "prosec": company.prosec,
"prosec_authorization": company.prosec_authorization, "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 Capa de servicio para lógica de negocio de empresa
""" """
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from sqlalchemy.exc import IntegrityError from sqlalchemy.exc import IntegrityError
from fastapi import HTTPException from fastapi import HTTPException
@@ -34,9 +35,14 @@ class CompanyService:
""" """
try: try:
# Verificar que no exista ya una empresa (solo puede haber una por el consecutivo único) # 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: 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 # Crear empresa
db_company = Company( db_company = Company(
@@ -69,7 +75,7 @@ class CompanyService:
ctpat_svi=company_data.ctpat_svi, ctpat_svi=company_data.ctpat_svi,
trusted_exporter_number=company_data.trusted_exporter_number, trusted_exporter_number=company_data.trusted_exporter_number,
prevalidator_key=company_data.prevalidator_key, prevalidator_key=company_data.prevalidator_key,
seventh_amendment=company_data.seventh_amendment seventh_amendment=company_data.seventh_amendment,
) )
self.db.add(db_company) self.db.add(db_company)
@@ -83,7 +89,10 @@ class CompanyService:
except IntegrityError as e: except IntegrityError as e:
self.db.rollback() self.db.rollback()
logger.error(f"IntegrityError creating company: {str(e)}") 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: except HTTPException:
raise raise
except Exception as e: except Exception as e:
@@ -118,7 +127,9 @@ class CompanyService:
return None return None
return CompanyResponseDTO.model_validate(company) 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 Actualiza una empresa
@@ -179,6 +190,26 @@ class CompanyService:
Returns: Returns:
True si existe una empresa, False en caso contrario 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 from pydantic import BaseModel
class CountryRuleOctBaseDTO(BaseModel): class CountryRuleOctBaseDTO(BaseModel):
permission: str permission: str
line: int line: int
fraction: str fraction: str
country_code: str country_code: str
class CountryRuleOctCreateDTO(CountryRuleOctBaseDTO): class CountryRuleOctCreateDTO(CountryRuleOctBaseDTO):
pass pass
class CountryRuleOctResponseDTO(CountryRuleOctBaseDTO): class CountryRuleOctResponseDTO(CountryRuleOctBaseDTO):
class Config: class Config:
from_attributes = True 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 sqlalchemy.orm import Mapped, mapped_column
from core.database import Base from core.database import Base
@@ -6,17 +13,35 @@ from core.database import Base
class CountryRuleOct(Base): class CountryRuleOct(Base):
__tablename__ = "country_rule_oct" __tablename__ = "country_rule_oct"
__table_args__ = ( __table_args__ = (
PrimaryKeyConstraint('id', name='country_rule_oct_pkey'), 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'),
ForeignKeyConstraint( ForeignKeyConstraint(
['tenant_id', 'company_id', 'permission', 'line', 'fraction'], ["tenant_id"], ["a76.tenants.id"], name="fk_country_rule_oct_tenant"
['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'), ForeignKeyConstraint(
{"schema": "a76"} ["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) id: Mapped[int] = mapped_column(Integer, primary_key=True)
@@ -27,4 +52,3 @@ class CountryRuleOct(Base):
line: Mapped[int] = mapped_column() line: Mapped[int] = mapped_column()
fraction: Mapped[str] = mapped_column(String(10)) fraction: Mapped[str] = mapped_column(String(10))
country_code: Mapped[str] = mapped_column(String(3)) 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]) @router.get("/", response_model=List[CountryRuleOctResponseDTO])
async def list_countries( async def list_countries(
db: Session = Depends(get_core_db), db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user)
): ):
""" """
List all CountryRuleOct entries. List all CountryRuleOct entries.
@@ -28,14 +27,17 @@ async def list_countries(
return db.query(CountryRuleOctService).all() 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( async def read_country_rule(
permission: str, permission: str,
line: int, line: int,
fraction: str, fraction: str,
country_code: str, country_code: str,
db: Session = Depends(get_core_db), 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. Get a specific CountryRuleOct by its composite key.
@@ -53,11 +55,13 @@ async def read_country_rule(
return country 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( async def create_country_rule(
country_data: CountryRuleOctCreateDTO, country_data: CountryRuleOctCreateDTO,
db: Session = Depends(get_core_db), 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. Create a new CountryRuleOct entry.
@@ -65,18 +69,23 @@ async def create_country_rule(
return CountryRuleOctService.create_country_rule(db, country_data) 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( async def delete_country_rule(
permission: str, permission: str,
line: int, line: int,
fraction: str, fraction: str,
country_code: str, country_code: str,
db: Session = Depends(get_core_db), 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. 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: if not country:
raise HTTPException(status_code=404, detail="CountryRuleOct not found") 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 sqlalchemy.orm import Session
from . import models, dto from . import models, dto
class CountryRuleOctService: class CountryRuleOctService:
@staticmethod @staticmethod
def get_country_by_keys(db: Session, permission: str, line: int, fraction: str, country_code: str): def get_country_by_keys(
return db.query(models.CountryRuleOct).filter( db: Session, permission: str, line: int, fraction: str, country_code: str
models.CountryRuleOct.permission == permission, ):
models.CountryRuleOct.line == line, return (
models.CountryRuleOct.fraction == fraction, db.query(models.CountryRuleOct)
models.CountryRuleOct.country_code == country_code .filter(
).first() models.CountryRuleOct.permission == permission,
models.CountryRuleOct.line == line,
models.CountryRuleOct.fraction == fraction,
models.CountryRuleOct.country_code == country_code,
)
.first()
)
@staticmethod @staticmethod
def create_country_rule(db: Session, country_data: dto.CountryRuleOctCreateDTO): def create_country_rule(db: Session, country_data: dto.CountryRuleOctCreateDTO):
@@ -24,8 +31,12 @@ class CountryRuleOctService:
return new_country return new_country
@staticmethod @staticmethod
def delete_country_rule(db: Session, permission: str, line: int, fraction: str, country_code: str): def delete_country_rule(
country = CountryRuleOctService.get_country_by_keys(db, permission, line, fraction, country_code) 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: if country:
db.delete(country) db.delete(country)
db.commit() db.commit()

View File

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

View File

@@ -1,6 +1,15 @@
from typing import Optional from typing import Optional
from decimal import Decimal 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 sqlalchemy.orm import Mapped, mapped_column
from core.database import Base from core.database import Base
@@ -8,11 +17,17 @@ from core.database import Base
class ExchangeRate(Base): class ExchangeRate(Base):
__tablename__ = "exchange_rate" __tablename__ = "exchange_rate"
__table_args__ = ( __table_args__ = (
PrimaryKeyConstraint('id', name='exchange_rate_pkey'), PrimaryKeyConstraint("id", name="exchange_rate_pkey"),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_exchange_rate_tenant'), ForeignKeyConstraint(
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_exchange_rate_company'), ["tenant_id"], ["a76.tenants.id"], name="fk_exchange_rate_tenant"
UniqueConstraint('tenant_id', 'company_id', 'date', name='uq_exchange_rate_date_tenant'), ),
{"schema": "a76"} 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) 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]) @router.get("/", response_model=List[ExchangeRateResponseDTO])
async def list_exchange_rates( async def list_exchange_rates(
db: Session = Depends(get_core_db), db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user)
): ):
""" """
List all ExchangeRate entries. List all ExchangeRate entries.
@@ -32,7 +31,7 @@ async def list_exchange_rates(
async def read_exchange_rate( async def read_exchange_rate(
date: int, date: int,
db: Session = Depends(get_core_db), 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. Get a specific ExchangeRate by its date.
@@ -50,11 +49,13 @@ async def read_exchange_rate(
return 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( async def create_exchange_rate(
exchange_rate_data: ExchangeRateCreateDTO, exchange_rate_data: ExchangeRateCreateDTO,
db: Session = Depends(get_core_db), 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. Create a new ExchangeRate entry.
@@ -73,7 +74,7 @@ async def create_exchange_rate(
async def delete_exchange_rate( async def delete_exchange_rate(
date: int, date: int,
db: Session = Depends(get_core_db), 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. Delete an ExchangeRate by its date.

View File

@@ -1,13 +1,20 @@
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from . import models, dto from . import models, dto
class ExchangeRateService: class ExchangeRateService:
@staticmethod @staticmethod
def get_exchange_rate_by_date(db: Session, date: int): 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 @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()) new_exchange_rate = models.ExchangeRate(**exchange_rate_data.dict())
db.add(new_exchange_rate) db.add(new_exchange_rate)
db.commit() db.commit()

View File

@@ -5,6 +5,7 @@ DTOs for FractionRuleOctave.
from pydantic import BaseModel from pydantic import BaseModel
from typing import Optional from typing import Optional
class FractionRuleOctaveBaseDTO(BaseModel): class FractionRuleOctaveBaseDTO(BaseModel):
PERMISSION: str PERMISSION: str
LINE: int LINE: int
@@ -16,9 +17,11 @@ class FractionRuleOctaveBaseDTO(BaseModel):
UNIT_COST_ME: Optional[float] UNIT_COST_ME: Optional[float]
UNIT_MEASURE: Optional[str] UNIT_MEASURE: Optional[str]
class FractionRuleOctaveCreateDTO(FractionRuleOctaveBaseDTO): class FractionRuleOctaveCreateDTO(FractionRuleOctaveBaseDTO):
pass pass
class FractionRuleOctaveResponseDTO(FractionRuleOctaveBaseDTO): class FractionRuleOctaveResponseDTO(FractionRuleOctaveBaseDTO):
class Config: class Config:
from_attributes = True 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 sqlalchemy.orm import Mapped, mapped_column
from core.database import Base from core.database import Base
@@ -6,11 +13,22 @@ from core.database import Base
class FractionRuleOctave(Base): class FractionRuleOctave(Base):
__tablename__ = "fraction_rule_octave" __tablename__ = "fraction_rule_octave"
__table_args__ = ( __table_args__ = (
PrimaryKeyConstraint('id', name='fraction_rule_octave_pkey'), PrimaryKeyConstraint("id", name="fraction_rule_octave_pkey"),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_fraction_rule_octave_tenant'), ForeignKeyConstraint(
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_fraction_rule_octave_company'), ["tenant_id"], ["a76.tenants.id"], name="fk_fraction_rule_octave_tenant"
UniqueConstraint('tenant_id', 'company_id', 'permission', 'line', 'fraction', name='uq_fraction_rule_octave_permission_line_fraction'), ),
{"schema": "a76"} 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) id: Mapped[int] = mapped_column(Integer, primary_key=True)
@@ -20,4 +38,3 @@ class FractionRuleOctave(Base):
permission: Mapped[str] = mapped_column(String(20)) permission: Mapped[str] = mapped_column(String(20))
line: Mapped[int] = mapped_column(Integer) line: Mapped[int] = mapped_column(Integer)
fraction: Mapped[str] = mapped_column(String(10)) 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]) @router.get("/", response_model=List[FractionRuleOctaveResponseDTO])
async def list_fractions( async def list_fractions(
db: Session = Depends(get_core_db), db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user)
): ):
""" """
List all FractionRuleOctave entries. List all FractionRuleOctave entries.
@@ -28,13 +27,15 @@ async def list_fractions(
return db.query(FractionRuleOctaveService).all() 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( async def read_fraction(
permission: str, permission: str,
line: int, line: int,
fraction: str, fraction: str,
db: Session = Depends(get_core_db), 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. Get a specific FractionRuleOctave by its composite key.
@@ -52,11 +53,15 @@ async def read_fraction(
return frac 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( async def create_frac(
frac_data: FractionRuleOctaveCreateDTO, frac_data: FractionRuleOctaveCreateDTO,
db: Session = Depends(get_core_db), 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. Create a new FractionRuleOctave entry.
@@ -71,13 +76,15 @@ async def create_frac(
return FractionRuleOctaveService.create_frac(db, frac_data) 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( async def delete_fraction(
permission: str, permission: str,
line: int, line: int,
fraction: str, fraction: str,
db: Session = Depends(get_core_db), 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. Delete a FractionRuleOctave by its composite key.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,10 +1,20 @@
""" """
Modelos ORM para gestión de partes/componentes Modelos ORM para gestión de partes/componentes
""" """
from typing import TYPE_CHECKING, Optional from typing import TYPE_CHECKING, Optional
from datetime import datetime from datetime import datetime
from decimal import Decimal 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.sql import func
from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.orm import Mapped, mapped_column, relationship
from core.database import Base 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) Modelo para la tabla GPartes - Información de partes en los sistemas SCAII (N), SCAF (S) Y WINSAAI (W)
""" """
__tablename__ = "parts" __tablename__ = "parts"
__table_args__ = ( __table_args__ = (
PrimaryKeyConstraint('id', name='parts_pkey'), PrimaryKeyConstraint("id", name="parts_pkey"),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_parts_tenant'), ForeignKeyConstraint(["tenant_id"], ["a76.tenants.id"], name="fk_parts_tenant"),
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_parts_company'), ForeignKeyConstraint(
ForeignKeyConstraint(['country_of_origin'], ['public.countries.m3_key'], name='fk_parts_country'), ["company_id"], ["a76.company.id"], name="fk_parts_company"
ForeignKeyConstraint(['currency_key'], ['public.currency_types.code'], name='fk_parts_currency'), ),
UniqueConstraint('tenant_id', 'company_id', 'part_number', name='client_part_ukey'), ForeignKeyConstraint(
{"schema": "a76"} ["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) 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)) fda_key: Mapped[Optional[str]] = mapped_column(String(20))
fcc_key: Mapped[Optional[str]] = mapped_column(String(30)) fcc_key: Mapped[Optional[str]] = mapped_column(String(30))
license_code: Mapped[Optional[str]] = mapped_column(String(3)) 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)) export_code: Mapped[Optional[str]] = mapped_column(String(2))
exclusion_symbol: Mapped[Optional[str]] = mapped_column(String(19)) # SIMBOLOEXCLIC 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) enabled_disabled: Mapped[Optional[int]] = mapped_column(SmallInteger)
creation_date: Mapped[Optional[int]] = mapped_column() # FECHACREACIONPARTE creation_date: Mapped[Optional[int]] = mapped_column() # FECHACREACIONPARTE
modification_date: Mapped[Optional[int]] = mapped_column() # FECHAMODIFICA 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 # Media
part_photo: Mapped[Optional[str]] = mapped_column(String(255)) part_photo: Mapped[Optional[str]] = mapped_column(String(255))
# Relationships # Relationships
country: Mapped[Optional["Country"]] = relationship(foreign_keys=[country_of_origin]) country: Mapped[Optional["Country"]] = relationship(
currency: Mapped[Optional["CurrencyType"]] = relationship(foreign_keys=[currency_key]) foreign_keys=[country_of_origin]
)
currency: Mapped[Optional["CurrencyType"]] = relationship(
foreign_keys=[currency_key]
)
# Relationship with Class through composite foreign 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 # 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)", primaryjoin="and_(Part.client_id == Class.client_id, Part.part_class == Class.class_code)",
foreign_keys="[Part.client_id, Part.part_class]", foreign_keys="[Part.client_id, Part.part_class]",
viewonly=True, viewonly=True,
back_populates="parts" back_populates="parts",
) )
def __repr__(self) -> str: def __repr__(self) -> str:
return f"<Part(client_id={self.client_id}, part_number='{self.part_number}', description='{self.description_spanish}')>" 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 Endpoints API para gestión de partes/componentes
""" """
from fastapi import APIRouter, Depends, HTTPException, Query, status from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from typing import List, Optional from typing import List, Optional
@@ -14,7 +15,7 @@ from .dto import (
PartResponseDTO, PartResponseDTO,
PartBasicDTO, PartBasicDTO,
PartListDTO, PartListDTO,
PartSearchDTO PartSearchDTO,
) )
router = APIRouter(prefix="/parts") router = APIRouter(prefix="/parts")
@@ -24,7 +25,7 @@ router = APIRouter(prefix="/parts")
async def create_part( async def create_part(
part_data: PartCreateDTO, part_data: PartCreateDTO,
db: Session = Depends(get_core_db), 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 Create a new part in the system
@@ -43,7 +44,9 @@ async def create_part(
@router.get("/", response_model=PartListDTO) @router.get("/", response_model=PartListDTO)
async def list_parts( async def list_parts(
skip: int = Query(0, ge=0, description="Number of records to skip"), 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"), client_id: Optional[int] = Query(None, description="Filter by client key"),
part_number: Optional[str] = Query(None, description="Search by part number"), part_number: Optional[str] = Query(None, description="Search by part number"),
description: Optional[str] = Query(None, description="Search in descriptions"), 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"), supplier: Optional[str] = Query(None, description="Filter by supplier"),
enabled_only: bool = Query(False, description="Show only enabled parts"), enabled_only: bool = Query(False, description="Show only enabled parts"),
db: Session = Depends(get_core_db), 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 List parts with optional filters and pagination
@@ -70,7 +73,7 @@ async def list_parts(
description=description, description=description,
fraction=fraction, fraction=fraction,
supplier=supplier, supplier=supplier,
enabled_only=enabled_only enabled_only=enabled_only,
) )
return service.list_parts(skip, limit, search_params) return service.list_parts(skip, limit, search_params)
@@ -81,7 +84,7 @@ async def get_parts_by_client(
skip: int = Query(0, ge=0), skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000), limit: int = Query(100, ge=1, le=1000),
db: Session = Depends(get_core_db), 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 Get all parts for a specific client
@@ -101,7 +104,7 @@ async def get_parts_by_client(
async def search_by_fraction( async def search_by_fraction(
fraction: str, fraction: str,
db: Session = Depends(get_core_db), 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 Search parts by tariff fraction
@@ -121,7 +124,7 @@ async def search_by_fraction(
async def search_by_supplier( async def search_by_supplier(
supplier: str, supplier: str,
db: Session = Depends(get_core_db), db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user) current_user: dict = Depends(get_current_user),
): ):
""" """
Search parts by supplier Search parts by supplier
@@ -141,7 +144,7 @@ async def search_by_supplier(
async def get_parts_by_country( async def get_parts_by_country(
country_code: str, country_code: str,
db: Session = Depends(get_core_db), 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 Get parts by country of origin
@@ -159,8 +162,7 @@ async def get_parts_by_country(
@router.get("/statistics", response_model=dict) @router.get("/statistics", response_model=dict)
async def get_parts_statistics( async def get_parts_statistics(
db: Session = Depends(get_core_db), db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user)
current_user: dict = Depends(get_current_user)
): ):
""" """
Get basic parts statistics Get basic parts statistics
@@ -181,7 +183,7 @@ async def get_part(
client_id: int, client_id: int,
part_number: str, part_number: str,
db: Session = Depends(get_core_db), 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) Get part by composite key (client_id + part_number)
@@ -198,7 +200,7 @@ async def get_part(
if not part: if not part:
raise HTTPException( raise HTTPException(
status_code=404, 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 return part
@@ -209,7 +211,7 @@ async def update_part(
part_number: str, part_number: str,
part_data: PartUpdateDTO, part_data: PartUpdateDTO,
db: Session = Depends(get_core_db), db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user) current_user: dict = Depends(get_current_user),
): ):
""" """
Update part information Update part information
@@ -226,7 +228,7 @@ async def update_part(
if not part: if not part:
raise HTTPException( raise HTTPException(
status_code=404, 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 return part
@@ -236,7 +238,7 @@ async def delete_part(
client_id: int, client_id: int,
part_number: str, part_number: str,
db: Session = Depends(get_core_db), 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 Delete part from the system
@@ -254,16 +256,18 @@ async def delete_part(
if not service.delete_part(client_id, part_number): if not service.delete_part(client_id, part_number):
raise HTTPException( raise HTTPException(
status_code=404, 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( async def toggle_part_status(
client_id: int, client_id: int,
part_number: str, part_number: str,
db: Session = Depends(get_core_db), 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 Toggle part enabled/disabled status
@@ -280,7 +284,7 @@ async def toggle_part_status(
if not part: if not part:
raise HTTPException( raise HTTPException(
status_code=404, 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 return part
@@ -291,7 +295,7 @@ async def get_part_basic_info(
client_id: int, client_id: int,
part_number: str, part_number: str,
db: Session = Depends(get_core_db), 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 Get basic information for a part
@@ -308,7 +312,7 @@ async def get_part_basic_info(
if not part: if not part:
raise HTTPException( raise HTTPException(
status_code=404, 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( return PartBasicDTO(
@@ -319,7 +323,7 @@ async def get_part_basic_info(
part_class=part.part_class, part_class=part.part_class,
unit_cost=part.unit_cost, unit_cost=part.unit_cost,
currency_key=part.currency_key, 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, client_id: int,
part_number: str, part_number: str,
db: Session = Depends(get_core_db), 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.) Get regulatory information for a part (FDA, FCC, ECCN, etc.)
@@ -345,7 +349,7 @@ async def get_part_regulatory_info(
if not part: if not part:
raise HTTPException( raise HTTPException(
status_code=404, 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 { return {
@@ -358,7 +362,5 @@ async def get_part_regulatory_info(
"license_code": part.license_code, "license_code": part.license_code,
"eccn": part.eccn, "eccn": part.eccn,
"export_code": part.export_code, "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 Capa de servicio para lógica de negocio de partes/componentes
""" """
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from sqlalchemy.exc import IntegrityError from sqlalchemy.exc import IntegrityError
from sqlalchemy import or_, and_, func from sqlalchemy import or_, and_, func
@@ -34,7 +35,10 @@ class PartService:
except IntegrityError as e: except IntegrityError as e:
db.rollback() db.rollback()
logger.error(f"Error creating part: {e}") 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: except Exception as e:
db.rollback() db.rollback()
logger.error(f"Unexpected error creating part: {e}") 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 Obtener una parte por clave de cliente y número de parte
""" """
try: try:
return db.query(Part).filter( return (
and_( db.query(Part)
Part.client_id == client_id, .filter(
Part.part_number == part_number and_(Part.client_id == client_id, Part.part_number == part_number)
) )
).first() .first()
)
except Exception as e: except Exception as e:
logger.error(f"Error getting part: {e}") logger.error(f"Error getting part: {e}")
raise HTTPException(status_code=500, detail="Error retrieving part") raise HTTPException(status_code=500, detail="Error retrieving part")
@@ -64,7 +69,7 @@ class PartService:
search: Optional[str] = None, search: Optional[str] = None,
client_id: Optional[int] = None, client_id: Optional[int] = None,
fraction: Optional[str] = None, fraction: Optional[str] = None,
country_of_origin: Optional[str] = None country_of_origin: Optional[str] = None,
) -> tuple[List[Part], int]: ) -> tuple[List[Part], int]:
""" """
Obtener partes con paginación y filtros Obtener partes con paginación y filtros
@@ -74,11 +79,13 @@ class PartService:
# Aplicar filtros # Aplicar filtros
if search: if search:
query = query.filter(or_( query = query.filter(
Part.description_spanish.ilike(f"%{search}%"), or_(
Part.description_english.ilike(f"%{search}%"), Part.description_spanish.ilike(f"%{search}%"),
Part.part_number.ilike(f"%{search}%") Part.description_english.ilike(f"%{search}%"),
)) Part.part_number.ilike(f"%{search}%"),
)
)
if client_id is not None: if client_id is not None:
query = query.filter(Part.client_id == client_id) query = query.filter(Part.client_id == client_id)
@@ -117,15 +124,21 @@ class PartService:
Buscar partes por fracción arancelaria Buscar partes por fracción arancelaria
""" """
try: try:
return db.query(Part).filter( return (
or_( db.query(Part)
Part.fraction.ilike(f"%{fraction}%"), .filter(
Part.us_fraction.ilike(f"%{fraction}%") or_(
Part.fraction.ilike(f"%{fraction}%"),
Part.us_fraction.ilike(f"%{fraction}%"),
)
) )
).all() .all()
)
except Exception as e: except Exception as e:
logger.error(f"Error searching parts by fraction: {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 @staticmethod
def search_parts_by_supplier(db: Session, supplier: str) -> List[Part]: 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() return db.query(Part).filter(Part.supplier.ilike(f"%{supplier}%")).all()
except Exception as e: except Exception as e:
logger.error(f"Error searching parts by supplier: {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 @staticmethod
def search_parts_by_country(db: Session, country_code: str) -> List[Part]: 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() return db.query(Part).filter(Part.country_of_origin == country_code).all()
except Exception as e: except Exception as e:
logger.error(f"Error searching parts by country: {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 @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 Actualizar una parte existente
""" """
@@ -190,7 +209,9 @@ class PartService:
raise HTTPException(status_code=500, detail="Error deleting part") raise HTTPException(status_code=500, detail="Error deleting part")
@staticmethod @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 Cambiar el estado habilitado/deshabilitado de una parte
""" """
@@ -219,17 +240,21 @@ class PartService:
total_parts = db.query(Part).count() total_parts = db.query(Part).count()
# Partes por cliente # Partes por cliente
parts_by_client = db.query( parts_by_client = (
Part.client_id, db.query(Part.client_id, func.count(Part.part_number).label("count"))
func.count(Part.part_number).label('count') .group_by(Part.client_id)
).group_by(Part.client_id).all() .all()
)
# Partes por país de origen # Partes por país de origen
parts_by_country = db.query( parts_by_country = (
Part.country_of_origin, db.query(
func.count(Part.part_number).label('count') 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() .filter(Part.country_of_origin.isnot(None))
.group_by(Part.country_of_origin)
.all()
)
# Partes habilitadas vs deshabilitadas # Partes habilitadas vs deshabilitadas
enabled_parts = db.query(Part).filter(Part.enabled_disabled == 1).count() enabled_parts = db.query(Part).filter(Part.enabled_disabled == 1).count()
@@ -239,15 +264,23 @@ class PartService:
"total_parts": total_parts, "total_parts": total_parts,
"enabled_parts": enabled_parts, "enabled_parts": enabled_parts,
"disabled_parts": disabled_parts, "disabled_parts": disabled_parts,
"parts_by_client": [{"client_id": item[0], "count": item[1]} for item in parts_by_client], "parts_by_client": [
"parts_by_country": [{"country": item[0], "count": item[1]} for item in parts_by_country] {"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: except Exception as e:
logger.error(f"Error getting parts statistics: {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 @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 Obtener información regulatoria específica de una parte
""" """
@@ -267,9 +300,10 @@ class PartService:
"eccn": db_part.eccn, "eccn": db_part.eccn,
"export_code": db_part.export_code, "export_code": db_part.export_code,
"exclusion_symbol": db_part.exclusion_symbol, "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: except Exception as e:
logger.error(f"Error getting part regulatory info: {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): class PedimentoConfigAdditionalBase(BaseModel):
"""Base schema for Pedimento Config Additional""" """Base schema for Pedimento Config Additional"""
pedimento_id: int = Field(..., description="Pedimento ID") pedimento_id: int = Field(..., description="Pedimento ID")
tenant_id: int = Field(..., description="Tenant ID") tenant_id: int = Field(..., description="Tenant ID")
add_po_identifier: Optional[int] = Field(None, description="Add PO identifier") 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") do_not_exempt_norms_complement_x: Optional[int] = Field(
manual_pedimento_year: Optional[str] = Field(None, max_length=2, description="Manual pedimento year") None, description="Do not exempt norms complement X"
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") 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") add_remove_norms: Optional[int] = Field(None, description="Add/remove norms")
class PedimentoConfigAdditionalCreate(PedimentoConfigAdditionalBase): class PedimentoConfigAdditionalCreate(PedimentoConfigAdditionalBase):
"""Schema for creating a new Pedimento Config Additional""" """Schema for creating a new Pedimento Config Additional"""
pass pass
class PedimentoConfigAdditionalUpdate(BaseModel): class PedimentoConfigAdditionalUpdate(BaseModel):
"""Schema for updating a Pedimento Config Additional""" """Schema for updating a Pedimento Config Additional"""
add_po_identifier: Optional[int] = None add_po_identifier: Optional[int] = None
do_not_exempt_norms_complement_x: Optional[int] = None do_not_exempt_norms_complement_x: Optional[int] = None
manual_pedimento_year: Optional[str] = Field(None, max_length=2) manual_pedimento_year: Optional[str] = Field(None, max_length=2)
@@ -32,6 +43,7 @@ class PedimentoConfigAdditionalUpdate(BaseModel):
class PedimentoConfigAdditionalResponse(PedimentoConfigAdditionalBase): class PedimentoConfigAdditionalResponse(PedimentoConfigAdditionalBase):
"""Schema for Pedimento Config Additional response""" """Schema for Pedimento Config Additional response"""
id: int id: int
created_at: datetime created_at: datetime

View File

@@ -5,27 +5,40 @@ from datetime import datetime
class PedimentoConfigCalculationsBase(BaseModel): class PedimentoConfigCalculationsBase(BaseModel):
"""Base schema for Pedimento Config Calculations""" """Base schema for Pedimento Config Calculations"""
pedimento_id: int = Field(..., description="Pedimento ID") pedimento_id: int = Field(..., description="Pedimento ID")
tenant_id: int = Field(..., description="Tenant ID") tenant_id: int = Field(..., description="Tenant ID")
dta_type: Optional[str] = Field(None, max_length=1, description="DTA type") dta_type: Optional[str] = Field(None, max_length=1, description="DTA type")
dta_operation: Optional[int] = Field(None, description="DTA operation") dta_operation: Optional[int] = Field(None, description="DTA operation")
dta_vehicle_count: Optional[int] = Field(None, description="DTA vehicle count") 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_vat: Optional[int] = Field(None, description="Pays VAT")
pays_prevalidation: Optional[int] = Field(None, description="Pays prevalidation") pays_prevalidation: Optional[int] = Field(None, description="Pays prevalidation")
include_sagar_certificate_fee: Optional[int] = Field(None, description="Include SAGAR certificate fee") include_sagar_certificate_fee: Optional[int] = Field(
fixed_vehicle_dta_fee: Optional[int] = Field(None, description="Fixed vehicle DTA fee") None, description="Include SAGAR certificate 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") 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): class PedimentoConfigCalculationsCreate(PedimentoConfigCalculationsBase):
"""Schema for creating a new Pedimento Config Calculations""" """Schema for creating a new Pedimento Config Calculations"""
pass pass
class PedimentoConfigCalculationsUpdate(BaseModel): class PedimentoConfigCalculationsUpdate(BaseModel):
"""Schema for updating a Pedimento Config Calculations""" """Schema for updating a Pedimento Config Calculations"""
dta_type: Optional[str] = Field(None, max_length=1) dta_type: Optional[str] = Field(None, max_length=1)
dta_operation: Optional[int] = None dta_operation: Optional[int] = None
dta_vehicle_count: Optional[int] = None dta_vehicle_count: Optional[int] = None
@@ -40,6 +53,7 @@ class PedimentoConfigCalculationsUpdate(BaseModel):
class PedimentoConfigCalculationsResponse(PedimentoConfigCalculationsBase): class PedimentoConfigCalculationsResponse(PedimentoConfigCalculationsBase):
"""Schema for Pedimento Config Calculations response""" """Schema for Pedimento Config Calculations response"""
id: int id: int
created_at: datetime created_at: datetime

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,16 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, PrimaryKeyConstraint, SmallInteger, String, UniqueConstraint, func, text from sqlalchemy import (
from sqlalchemy.orm import Mapped, mapped_column, relationship DateTime,
ForeignKeyConstraint,
Integer,
PrimaryKeyConstraint,
SmallInteger,
String,
UniqueConstraint,
func,
text,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
from datetime import datetime from datetime import datetime
from core.database import Base from core.database import Base
@@ -9,31 +19,55 @@ if TYPE_CHECKING:
class PedimentoConfigAdditional(Base): class PedimentoConfigAdditional(Base):
__tablename__ = 'pedimento_config_additional' __tablename__ = "pedimento_config_additional"
__table_args__ = ( __table_args__ = (
PrimaryKeyConstraint('id', name='pedimento_config_additional_pkey'), PrimaryKeyConstraint("id", name="pedimento_config_additional_pkey"),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimento_config_additional_tenant'), ForeignKeyConstraint(
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimento_config_additional_company'), ["tenant_id"],
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_config_additional'), ["a76.tenants.id"],
UniqueConstraint('tenant_id', 'company_id', 'pedimento_id', name='pedimento_config_additional_pedimento_id_key'), name="fk_pedimento_config_additional_tenant",
{'schema': 'a76'} ),
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) id: Mapped[int] = mapped_column(Integer)
tenant_id: Mapped [int] = mapped_column(Integer, nullable=False, index=True) tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
company_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) add_po_identifier: Mapped[int] = mapped_column(SmallInteger)
do_not_exempt_norms_complement_x: 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)) manual_pedimento_year: Mapped[str] = mapped_column(String(2))
enable_import_invoice_recipient: Mapped [int] = mapped_column(SmallInteger) enable_import_invoice_recipient: Mapped[int] = mapped_column(SmallInteger)
send_502_validation_file_for_consolidated: 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_remove_norms: Mapped[int] = mapped_column(SmallInteger)
# Timestamps # Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now()) created_at: Mapped[datetime] = mapped_column(
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now()) 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) 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 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 sqlalchemy.orm import Mapped, mapped_column, relationship
from datetime import datetime from datetime import datetime
from core.database import Base from core.database import Base
@@ -7,15 +17,26 @@ from core.database import Base
if TYPE_CHECKING: if TYPE_CHECKING:
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
class PedimentoConfigCalculations(Base): class PedimentoConfigCalculations(Base):
__tablename__ = 'pedimento_config_calculations' __tablename__ = "pedimento_config_calculations"
__table_args__ = ( __table_args__ = (
PrimaryKeyConstraint('id', name='pedimento_config_calculations_pkey'), PrimaryKeyConstraint("id", name="pedimento_config_calculations_pkey"),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id']), ForeignKeyConstraint(["tenant_id"], ["a76.tenants.id"]),
ForeignKeyConstraint(['company_id'], ['a76.company.id']), ForeignKeyConstraint(["company_id"], ["a76.company.id"]),
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_config_calculations'), ForeignKeyConstraint(
UniqueConstraint('tenant_id', 'company_id', 'pedimento_id', name='pedimento_config_calculations_pedimento_id_key'), ["pedimento_id"],
{'schema': 'a76'} ["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) id: Mapped[int] = mapped_column(Integer)
@@ -35,8 +56,14 @@ class PedimentoConfigCalculations(Base):
additional_fixed_fee_payment_method: Mapped[int] = mapped_column(SmallInteger) additional_fixed_fee_payment_method: Mapped[int] = mapped_column(SmallInteger)
# Timestamps # Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now()) created_at: Mapped[datetime] = mapped_column(
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now()) 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) 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 decimal import Decimal
from typing import TYPE_CHECKING 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 import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped from sqlalchemy.orm.base import Mapped
from datetime import datetime from datetime import datetime
@@ -11,14 +21,32 @@ if TYPE_CHECKING:
class PedimentoConfigParameters(Base): class PedimentoConfigParameters(Base):
__tablename__ = 'pedimento_config_parameters' __tablename__ = "pedimento_config_parameters"
__table_args__ = ( __table_args__ = (
PrimaryKeyConstraint('id', name='pedimento_config_parameters_pkey'), PrimaryKeyConstraint("id", name="pedimento_config_parameters_pkey"),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimento_config_parameters_tenant'), ForeignKeyConstraint(
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimento_config_parameters_company'), ["tenant_id"],
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_config_parameters'), ["a76.tenants.id"],
UniqueConstraint('tenant_id', 'company_id', 'pedimento_id', name='pedimento_config_parameters_pedimento_id_key'), name="fk_pedimento_config_parameters_tenant",
{'schema': 'a76'} ),
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) id: Mapped[int] = mapped_column(Integer)
@@ -39,8 +67,14 @@ class PedimentoConfigParameters(Base):
is_consolidated: Mapped[int] = mapped_column(SmallInteger) is_consolidated: Mapped[int] = mapped_column(SmallInteger)
# Timestamps # Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now()) created_at: Mapped[datetime] = mapped_column(
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now()) 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) 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 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 import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped from sqlalchemy.orm.base import Mapped
from datetime import datetime from datetime import datetime
@@ -8,15 +17,34 @@ from core.database import Base
if TYPE_CHECKING: if TYPE_CHECKING:
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
class PedimentoConfigSurcharges(Base): class PedimentoConfigSurcharges(Base):
__tablename__ = 'pedimento_config_surcharges' __tablename__ = "pedimento_config_surcharges"
__table_args__ = ( __table_args__ = (
PrimaryKeyConstraint('id', name='pedimento_config_surcharges_pkey'), PrimaryKeyConstraint("id", name="pedimento_config_surcharges_pkey"),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimento_config_surcharges_tenant'), ForeignKeyConstraint(
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimento_config_surcharges_company'), ["tenant_id"],
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_config_surcharges'), ["a76.tenants.id"],
UniqueConstraint('tenant_id', 'company_id', 'pedimento_id', name='pedimento_config_surcharges_pedimento_id_key'), name="fk_pedimento_config_surcharges_tenant",
{'schema': 'a76'} ),
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) id: Mapped[int] = mapped_column(Integer)
@@ -32,8 +60,14 @@ class PedimentoConfigSurcharges(Base):
surcharge_cc: Mapped[int] = mapped_column(SmallInteger) surcharge_cc: Mapped[int] = mapped_column(SmallInteger)
# Timestamps # Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now()) created_at: Mapped[datetime] = mapped_column(
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now()) 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) 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 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 import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped from sqlalchemy.orm.base import Mapped
from datetime import datetime from datetime import datetime
@@ -8,15 +17,34 @@ from core.database import Base
if TYPE_CHECKING: if TYPE_CHECKING:
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
class PedimentoConfigUpdateRectification(Base): class PedimentoConfigUpdateRectification(Base):
__tablename__ = 'pedimento_config_update_rectification' __tablename__ = "pedimento_config_update_rectification"
__table_args__ = ( __table_args__ = (
PrimaryKeyConstraint('id', name='pedimento_config_update_rectification_pkey'), PrimaryKeyConstraint("id", name="pedimento_config_update_rectification_pkey"),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimento_config_update_rectification_tenant'), ForeignKeyConstraint(
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimento_config_update_rectification_company'), ["tenant_id"],
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_config_update_rectification'), ["a76.tenants.id"],
UniqueConstraint('tenant_id', 'company_id', 'pedimento_id', name='pedimento_config_update_rectification_pedimento_id_key'), name="fk_pedimento_config_update_rectification_tenant",
{'schema': 'a76'} ),
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) id: Mapped[int] = mapped_column(Integer)
@@ -31,8 +59,14 @@ class PedimentoConfigUpdateRectification(Base):
calculate_surcharge: Mapped[int] = mapped_column(SmallInteger) calculate_surcharge: Mapped[int] = mapped_column(SmallInteger)
# Timestamps # Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now()) created_at: Mapped[datetime] = mapped_column(
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now()) 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) 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 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 import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped from sqlalchemy.orm.base import Mapped
from datetime import datetime from datetime import datetime
@@ -8,15 +17,32 @@ from core.database import Base
if TYPE_CHECKING: if TYPE_CHECKING:
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
class PedimentoConfigUpdates(Base): class PedimentoConfigUpdates(Base):
__tablename__ = 'pedimento_config_updates' __tablename__ = "pedimento_config_updates"
__table_args__ = ( __table_args__ = (
PrimaryKeyConstraint('id', name='pedimento_config_updates_pkey'), PrimaryKeyConstraint("id", name="pedimento_config_updates_pkey"),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimento_config_updates_tenant'), ForeignKeyConstraint(
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimento_config_updates_company'), ["tenant_id"], ["a76.tenants.id"], name="fk_pedimento_config_updates_tenant"
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'), ForeignKeyConstraint(
{'schema': 'a76'} ["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) id: Mapped[int] = mapped_column(Integer)
@@ -30,8 +56,14 @@ class PedimentoConfigUpdates(Base):
update_ieps: Mapped[int] = mapped_column(SmallInteger) update_ieps: Mapped[int] = mapped_column(SmallInteger)
# Timestamps # Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now()) created_at: Mapped[datetime] = mapped_column(
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now()) 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) 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 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 import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped from sqlalchemy.orm.base import Mapped
from datetime import datetime from datetime import datetime
@@ -8,15 +17,34 @@ from core.database import Base
if TYPE_CHECKING: if TYPE_CHECKING:
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
class PedimentoCustomsOffices(Base): class PedimentoCustomsOffices(Base):
__tablename__ = 'pedimento_customs_offices' __tablename__ = "pedimento_customs_offices"
__table_args__ = ( __table_args__ = (
PrimaryKeyConstraint('id', name='pedimento_customs_offices_pkey'), PrimaryKeyConstraint("id", name="pedimento_customs_offices_pkey"),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimento_customs_offices_tenant'), ForeignKeyConstraint(
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimento_customs_offices_company'), ["tenant_id"],
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_customs_offices'), ["a76.tenants.id"],
UniqueConstraint('tenant_id', 'company_id', 'pedimento_id', name='pedimento_customs_offices_pedimento_id_key'), name="fk_pedimento_customs_offices_tenant",
{'schema': 'a76'} ),
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) id: Mapped[int] = mapped_column(Integer)
@@ -28,8 +56,14 @@ class PedimentoCustomsOffices(Base):
entry_exit_customs: Mapped[str] = mapped_column(String(3)) entry_exit_customs: Mapped[str] = mapped_column(String(3))
# Timestamps # Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now()) created_at: Mapped[datetime] = mapped_column(
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now()) 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) 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 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 import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped from sqlalchemy.orm.base import Mapped
from datetime import datetime, time as datetime_time from datetime import datetime, time as datetime_time
@@ -8,16 +18,31 @@ from core.database import Base
if TYPE_CHECKING: if TYPE_CHECKING:
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
class PedimentoDates(Base): class PedimentoDates(Base):
__tablename__ = 'pedimento_dates' __tablename__ = "pedimento_dates"
__table_args__ = ( __table_args__ = (
PrimaryKeyConstraint('id', name='pedimento_dates_pkey'), PrimaryKeyConstraint("id", name="pedimento_dates_pkey"),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimento_dates_tenant'), ForeignKeyConstraint(
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimento_dates_company'), ["tenant_id"], ["a76.tenants.id"], name="fk_pedimento_dates_tenant"
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'), ForeignKeyConstraint(
Index('idx_pedimento_dates_pedimento_id', 'pedimento_id'), ["company_id"], ["a76.company.id"], name="fk_pedimento_dates_company"
{'schema': 'a76'} ),
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) id: Mapped[int] = mapped_column(Integer)
@@ -39,8 +64,14 @@ class PedimentoDates(Base):
capture_time: Mapped[datetime_time] = mapped_column(Time) capture_time: Mapped[datetime_time] = mapped_column(Time)
# Timestamps # Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now()) created_at: Mapped[datetime] = mapped_column(
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now()) 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) 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 decimal import Decimal
from typing import TYPE_CHECKING 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 import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped from sqlalchemy.orm.base import Mapped
from datetime import datetime from datetime import datetime
@@ -9,15 +20,32 @@ from core.database import Base
if TYPE_CHECKING: if TYPE_CHECKING:
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
class PedimentoDecrementables(Base): class PedimentoDecrementables(Base):
__tablename__ = 'pedimento_decrementables' __tablename__ = "pedimento_decrementables"
__table_args__ = ( __table_args__ = (
PrimaryKeyConstraint('id', name='pedimento_decrementables_pkey'), PrimaryKeyConstraint("id", name="pedimento_decrementables_pkey"),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimento_decrementables_tenant'), ForeignKeyConstraint(
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimento_decrementables_company'), ["tenant_id"], ["a76.tenants.id"], name="fk_pedimento_decrementables_tenant"
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'), ForeignKeyConstraint(
{'schema': 'a76'} ["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) id: Mapped[int] = mapped_column(Integer)
@@ -36,8 +64,14 @@ class PedimentoDecrementables(Base):
not_affect_customs_value: Mapped[int] = mapped_column(SmallInteger) not_affect_customs_value: Mapped[int] = mapped_column(SmallInteger)
# Timestamps # Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now()) created_at: Mapped[datetime] = mapped_column(
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now()) 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) 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 decimal import Decimal
from typing import TYPE_CHECKING 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 import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped from sqlalchemy.orm.base import Mapped
from datetime import datetime from datetime import datetime
@@ -9,15 +20,32 @@ from core.database import Base
if TYPE_CHECKING: if TYPE_CHECKING:
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
class PedimentoIncrementables(Base): class PedimentoIncrementables(Base):
__tablename__ = 'pedimento_incrementables' __tablename__ = "pedimento_incrementables"
__table_args__ = ( __table_args__ = (
PrimaryKeyConstraint('id', name='pedimento_incrementables_pkey'), PrimaryKeyConstraint("id", name="pedimento_incrementables_pkey"),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimento_incrementables_tenant'), ForeignKeyConstraint(
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimento_incrementables_company'), ["tenant_id"], ["a76.tenants.id"], name="fk_pedimento_incrementables_tenant"
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'), ForeignKeyConstraint(
{'schema': 'a76'} ["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) id: Mapped[int] = mapped_column(Integer)
@@ -37,8 +65,14 @@ class PedimentoIncrementables(Base):
not_affect_customs_value: Mapped[int] = mapped_column(SmallInteger) not_affect_customs_value: Mapped[int] = mapped_column(SmallInteger)
# Timestamps # Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now()) created_at: Mapped[datetime] = mapped_column(
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now()) 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) 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 decimal import Decimal
from typing import TYPE_CHECKING 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 import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped from sqlalchemy.orm.base import Mapped
from datetime import datetime from datetime import datetime
@@ -9,15 +19,30 @@ from core.database import Base
if TYPE_CHECKING: if TYPE_CHECKING:
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
class PedimentoIndexes(Base): class PedimentoIndexes(Base):
__tablename__ = 'pedimento_indexes' __tablename__ = "pedimento_indexes"
__table_args__ = ( __table_args__ = (
PrimaryKeyConstraint('id', name='pedimento_indexes_pkey'), PrimaryKeyConstraint("id", name="pedimento_indexes_pkey"),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimento_indexes_tenant'), ForeignKeyConstraint(
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimento_indexes_company'), ["tenant_id"], ["a76.tenants.id"], name="fk_pedimento_indexes_tenant"
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'), ForeignKeyConstraint(
{'schema': 'a76'} ["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) id: Mapped[int] = mapped_column(Integer)
@@ -30,8 +55,14 @@ class PedimentoIndexes(Base):
manual_update_factor: Mapped[int] = mapped_column(SmallInteger) manual_update_factor: Mapped[int] = mapped_column(SmallInteger)
# Timestamps # Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now()) created_at: Mapped[datetime] = mapped_column(
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now()) 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) 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 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 import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped from sqlalchemy.orm.base import Mapped
from datetime import datetime, time as Time2, date as Date2 from datetime import datetime, time as Time2, date as Date2
@@ -8,16 +21,31 @@ from core.database import Base
if TYPE_CHECKING: if TYPE_CHECKING:
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
class PedimentoPayments(Base): class PedimentoPayments(Base):
__tablename__ = 'pedimento_payments' __tablename__ = "pedimento_payments"
__table_args__ = ( __table_args__ = (
PrimaryKeyConstraint('id', name='pedimento_payments_pkey'), PrimaryKeyConstraint("id", name="pedimento_payments_pkey"),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimento_payments_tenant'), ForeignKeyConstraint(
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimento_payments_company'), ["tenant_id"], ["a76.tenants.id"], name="fk_pedimento_payments_tenant"
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'), ForeignKeyConstraint(
Index('idx_pedimento_payments_pedimento_id', 'pedimento_id'), ["company_id"], ["a76.company.id"], name="fk_pedimento_payments_company"
{'schema': 'a76'} ),
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) id: Mapped[int] = mapped_column(Integer)
@@ -39,8 +67,14 @@ class PedimentoPayments(Base):
pece_code: Mapped[str] = mapped_column(String(5)) pece_code: Mapped[str] = mapped_column(String(5))
# Timestamps # Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now()) created_at: Mapped[datetime] = mapped_column(
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now()) 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) 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 datetime import datetime
from typing import TYPE_CHECKING 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 import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped from sqlalchemy.orm.base import Mapped
from core.database import Base from core.database import Base
@@ -8,15 +16,34 @@ from core.database import Base
if TYPE_CHECKING: if TYPE_CHECKING:
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
class PedimentoRectificationDestination(Base): class PedimentoRectificationDestination(Base):
__tablename__ = 'pedimento_rectification_destination' __tablename__ = "pedimento_rectification_destination"
__table_args__ = ( __table_args__ = (
PrimaryKeyConstraint('id', name='pedimento_rectification_destination_pkey'), PrimaryKeyConstraint("id", name="pedimento_rectification_destination_pkey"),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimento_rectification_destination_tenant'), ForeignKeyConstraint(
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimento_rectification_destination_company'), ["tenant_id"],
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_rectification_destination'), ["a76.tenants.id"],
UniqueConstraint('tenant_id', 'company_id', 'pedimento_id', name='pedimento_rectification_destination_pedimento_id_key'), name="fk_pedimento_rectification_destination_tenant",
{'schema': 'a76'} ),
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) id: Mapped[int] = mapped_column(Integer)
@@ -30,8 +57,14 @@ class PedimentoRectificationDestination(Base):
destination_pedimento_number: Mapped[str] = mapped_column(String(7)) destination_pedimento_number: Mapped[str] = mapped_column(String(7))
# Timestamps # Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now()) created_at: Mapped[datetime] = mapped_column(
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now()) 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) 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 datetime import datetime
from typing import TYPE_CHECKING 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 import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped from sqlalchemy.orm.base import Mapped
from core.database import Base from core.database import Base
@@ -8,15 +17,34 @@ from core.database import Base
if TYPE_CHECKING: if TYPE_CHECKING:
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
class PedimentoRectificationOrigin(Base): class PedimentoRectificationOrigin(Base):
__tablename__ = 'pedimento_rectification_origin' __tablename__ = "pedimento_rectification_origin"
__table_args__ = ( __table_args__ = (
PrimaryKeyConstraint('id', name='pedimento_rectification_origin_pkey'), PrimaryKeyConstraint("id", name="pedimento_rectification_origin_pkey"),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimento_rectification_origin_tenant'), ForeignKeyConstraint(
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimento_rectification_origin_company'), ["tenant_id"],
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_rectification_origin'), ["a76.tenants.id"],
UniqueConstraint('tenant_id', 'company_id', 'pedimento_id', name='pedimento_rectification_origin_pedimento_id_key'), name="fk_pedimento_rectification_origin_tenant",
{'schema': 'a76'} ),
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) id: Mapped[int] = mapped_column(Integer)
@@ -34,13 +62,21 @@ class PedimentoRectificationOrigin(Base):
total_others: Mapped[int] = mapped_column(Integer) total_others: Mapped[int] = mapped_column(Integer)
reason: Mapped[str] = mapped_column(String(255)) reason: Mapped[str] = mapped_column(String(255))
charge_to_client: Mapped[int] = mapped_column(SmallInteger) 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) manual_calculation: Mapped[int] = mapped_column(SmallInteger)
original_pedimento_norms: Mapped[int] = mapped_column(SmallInteger) original_pedimento_norms: Mapped[int] = mapped_column(SmallInteger)
# Timestamps # Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now()) created_at: Mapped[datetime] = mapped_column(
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now()) 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) 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 datetime import datetime
from typing import TYPE_CHECKING 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 import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped from sqlalchemy.orm.base import Mapped
from core.database import Base from core.database import Base
@@ -8,15 +17,34 @@ from core.database import Base
if TYPE_CHECKING: if TYPE_CHECKING:
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
class PedimentoTransportMeans(Base): class PedimentoTransportMeans(Base):
__tablename__ = 'pedimento_transport_means' __tablename__ = "pedimento_transport_means"
__table_args__ = ( __table_args__ = (
PrimaryKeyConstraint('id', name='pedimento_transport_means_pkey'), PrimaryKeyConstraint("id", name="pedimento_transport_means_pkey"),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimento_transport_means_tenant'), ForeignKeyConstraint(
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimento_transport_means_company'), ["tenant_id"],
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_transport_means'), ["a76.tenants.id"],
UniqueConstraint('tenant_id', 'company_id', 'pedimento_id', name='pedimento_transport_means_pedimento_id_key'), name="fk_pedimento_transport_means_tenant",
{'schema': 'a76'} ),
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) id: Mapped[int] = mapped_column(Integer)
@@ -28,6 +56,10 @@ class PedimentoTransportMeans(Base):
entry_exit: Mapped[str] = mapped_column(String(2)) entry_exit: Mapped[str] = mapped_column(String(2))
arrival: Mapped[str] = mapped_column(String(2)) arrival: Mapped[str] = mapped_column(String(2))
departure: 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 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 import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped from sqlalchemy.orm.base import Mapped
from datetime import datetime from datetime import datetime
@@ -8,14 +17,25 @@ from core.database import Base
if TYPE_CHECKING: if TYPE_CHECKING:
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
class PedimentoValidation(Base): class PedimentoValidation(Base):
__tablename__ = 'pedimento_validation' #PedimentoValidacion __tablename__ = "pedimento_validation" # PedimentoValidacion
__table_args__ = ( __table_args__ = (
PrimaryKeyConstraint('id', name='pedimento_validation_pkey'), PrimaryKeyConstraint("id", name="pedimento_validation_pkey"),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id']), ForeignKeyConstraint(["tenant_id"], ["a76.tenants.id"]),
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_validation'), ForeignKeyConstraint(
UniqueConstraint('tenant_id', 'company_id', 'pedimento_id', name='pedimento_validation_pedimento_id_key'), ["pedimento_id"],
{'schema': 'a76'} ["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) id: Mapped[int] = mapped_column(Integer)
@@ -23,20 +43,24 @@ class PedimentoValidation(Base):
company_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)
validator: Mapped[str] = mapped_column(String(3)) #validador validator: Mapped[str] = mapped_column(String(3)) # validador
validation_ack: Mapped[str] = mapped_column(String(8)) #acuse_validacion validation_ack: Mapped[str] = mapped_column(String(8)) # acuse_validacion
pre_ack: Mapped[str] = mapped_column(String(8)) #acuse_previo pre_ack: Mapped[str] = mapped_column(String(8)) # acuse_previo
line_signature: Mapped[str] = mapped_column(String(50)) #firma_linea_captura line_signature: Mapped[str] = mapped_column(String(50)) # firma_linea_captura
electronic_signature: Mapped[str] = mapped_column(String(999)) #firma_electronica electronic_signature: Mapped[str] = mapped_column(String(999)) # firma_electronica
certificate_number: Mapped[str] = mapped_column(String(99)) #numero_certificado certificate_number: Mapped[str] = mapped_column(String(99)) # numero_certificado
validator_id: Mapped[int] = mapped_column(Integer) validator_id: Mapped[int] = mapped_column(Integer)
responsible_id: Mapped[int] = mapped_column(Integer) responsible_id: Mapped[int] = mapped_column(Integer)
# Timestamps # Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now()) created_at: Mapped[datetime] = mapped_column(
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now()) 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) 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 decimal import Decimal
from typing import TYPE_CHECKING, Optional 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 import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped from sqlalchemy.orm.base import Mapped
from datetime import datetime from datetime import datetime
@@ -8,37 +19,86 @@ from enum import IntEnum
from core.database import Base from core.database import Base
if TYPE_CHECKING: if TYPE_CHECKING:
from api.v1.modules.a76.pedmientos.models.pedimento_config_additional import PedimentoConfigAdditional from api.v1.modules.a76.pedmientos.models.pedimento_config_additional import (
from api.v1.modules.a76.pedmientos.models.pedimento_config_calculations import PedimentoConfigCalculations PedimentoConfigAdditional,
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_calculations import (
from api.v1.modules.a76.pedmientos.models.pedimento_config_update_rectification import PedimentoConfigUpdateRectification PedimentoConfigCalculations,
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_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_dates import PedimentoDates
from api.v1.modules.a76.pedmientos.models.pedimento_decrementables import PedimentoDecrementables from api.v1.modules.a76.pedmientos.models.pedimento_decrementables import (
from api.v1.modules.a76.pedmientos.models.pedimento_incrementables import PedimentoIncrementables 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_indexes import PedimentoIndexes
from api.v1.modules.a76.pedmientos.models.pedimento_payments import PedimentoPayments from api.v1.modules.a76.pedmientos.models.pedimento_payments import (
from api.v1.modules.a76.pedmientos.models.pedimento_rectification_destination import PedimentoRectificationDestination PedimentoPayments,
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_rectification_destination import (
from api.v1.modules.a76.pedmientos.models.pedimento_validation import PedimentoValidation 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): class Pedimentos(Base):
__tablename__ = 'pedimentos' __tablename__ = "pedimentos"
__table_args__ = ( __table_args__ = (
PrimaryKeyConstraint('id', name='pedimentos_pkey'), PrimaryKeyConstraint("id", name="pedimentos_pkey"),
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], name='fk_pedimentos_tenant'), ForeignKeyConstraint(
ForeignKeyConstraint(['company_id'], ['a76.company.id'], name='fk_pedimentos_company'), ["tenant_id"], ["a76.tenants.id"], name="fk_pedimentos_tenant"
ForeignKeyConstraint(['client_id'], ['a76.client_provider.id'], name='fk_pedimentos_client'), ),
ForeignKeyConstraint(['regime'], ['public.pedimento_regimens.code'], name='fk_pedimentos_regime'), ForeignKeyConstraint(
ForeignKeyConstraint(['pedimento_code'], ['public.pedimento_codes.code'], name='fk_pedimentos_code'), ["company_id"], ["a76.company.id"], name="fk_pedimentos_company"
UniqueConstraint('tenant_id', 'company_id', 'year', 'customs_office', 'license', 'pedimento_number', name='pedimentos_unique_key'), ),
Index('idx_pedimentos_client_id', 'client_id'), ForeignKeyConstraint(
Index('idx_pedimentos_created_at', 'created_at'), ["client_id"], ["a76.client_provider.id"], name="fk_pedimentos_client"
Index('idx_pedimentos_status', 'status'), ),
{'schema': 'a76'} 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) id: Mapped[int] = mapped_column(Integer)
@@ -61,24 +121,67 @@ class Pedimentos(Base):
exchange_rate: Mapped[Optional[Decimal]] = mapped_column(Numeric(9, 5)) exchange_rate: Mapped[Optional[Decimal]] = mapped_column(Numeric(9, 5))
# Timestamps # Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now()) created_at: Mapped[datetime] = mapped_column(
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now()) 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) deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
pedimento_config_additional: Mapped['PedimentoConfigAdditional'] = relationship('PedimentoConfigAdditional', uselist=False, back_populates='pedimento') pedimento_config_additional: Mapped["PedimentoConfigAdditional"] = relationship(
pedimento_config_calculations: Mapped['PedimentoConfigCalculations'] = relationship('PedimentoConfigCalculations', uselist=False, back_populates='pedimento') "PedimentoConfigAdditional", 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_calculations: Mapped["PedimentoConfigCalculations"] = relationship(
pedimento_config_update_rectification: Mapped['PedimentoConfigUpdateRectification'] = relationship('PedimentoConfigUpdateRectification', uselist=False, back_populates='pedimento') "PedimentoConfigCalculations", 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_config_parameters: Mapped["PedimentoConfigParameters"] = relationship(
pedimento_dates: Mapped['PedimentoDates'] = relationship('PedimentoDates', uselist=False, back_populates='pedimento') "PedimentoConfigParameters", 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_config_surcharges: Mapped["PedimentoConfigSurcharges"] = relationship(
pedimento_indexes: Mapped['PedimentoIndexes'] = relationship('PedimentoIndexes', uselist=False, back_populates='pedimento') "PedimentoConfigSurcharges", 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_config_update_rectification: Mapped[
pedimento_rectification_origin: Mapped['PedimentoRectificationOrigin'] = relationship('PedimentoRectificationOrigin', uselist=False, back_populates='pedimento') "PedimentoConfigUpdateRectification"
pedimento_transport_means: Mapped['PedimentoTransportMeans'] = relationship('PedimentoTransportMeans', uselist=False, back_populates='pedimento') ] = relationship(
pedimento_validation: Mapped['PedimentoValidation'] = relationship('PedimentoValidation', uselist=False, back_populates='pedimento') "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 fastapi import APIRouter
from .routes.pedimento_config_additional import router as pedimento_config_additional_router from .routes.pedimento_config_additional import (
from .routes.pedimento_config_calculations import router as pedimento_config_calculations_router router as pedimento_config_additional_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_calculations import (
from .routes.pedimento_config_update_rectification import router as pedimento_config_update_rectification_router 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_config_updates import router as pedimento_config_updates_router
from .routes.pedimento_customs_offices import router as pedimento_customs_offices_router from .routes.pedimento_customs_offices import router as pedimento_customs_offices_router
from .routes.pedimento_dates import router as pedimento_dates_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_incrementables import router as pedimento_incrementables_router
from .routes.pedimento_indexes import router as pedimento_indexes_router from .routes.pedimento_indexes import router as pedimento_indexes_router
from .routes.pedimento_payments import router as pedimento_payments_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_destination import (
from .routes.pedimento_rectification_origin import router as pedimento_rectification_origin_router 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_transport_means import router as pedimento_transport_means_router
from .routes.pedimento_validation import router as pedimento_validation_router from .routes.pedimento_validation import router as pedimento_validation_router
from .routes.pedimentos import router as pedimentos_router from .routes.pedimentos import router as pedimentos_router
router = APIRouter() router = APIRouter()
router.include_router(pedimento_config_additional_router, prefix="/pedimentos", tags=["a76 / pedimentos / pedimento_config_additional"]) router.include_router(
router.include_router(pedimento_config_calculations_router, prefix="/pedimentos", tags=["a76 / pedimentos / pedimento_config_calculations"]) pedimento_config_additional_router,
router.include_router(pedimento_config_parameters_router, prefix="/pedimentos", tags=["a76 / pedimentos / pedimento_config_parameters"]) prefix="/pedimentos",
router.include_router(pedimento_config_surcharges_router, prefix="/pedimentos", tags=["a76 / pedimentos / pedimento_config_surcharges"]) tags=["a76 / pedimentos / pedimento_config_additional"],
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(
router.include_router(pedimento_customs_offices_router, prefix="/pedimentos", tags=["a76 / pedimentos / pedimento_customs_offices"]) pedimento_config_calculations_router,
router.include_router(pedimento_dates_router, prefix="/pedimentos", tags=["a76 / pedimentos / pedimento_dates"]) prefix="/pedimentos",
router.include_router(pedimento_decrementables_router, prefix="/pedimentos", tags=["a76 / pedimentos / pedimento_decrementables"]) tags=["a76 / pedimentos / pedimento_config_calculations"],
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(
router.include_router(pedimento_payments_router, prefix="/pedimentos", tags=["a76 / pedimentos / pedimento_payments"]) pedimento_config_parameters_router,
router.include_router(pedimento_rectification_destination_router, prefix="/pedimentos", tags=["a76 / pedimentos / pedimento_rectification_destination"]) prefix="/pedimentos",
router.include_router(pedimento_rectification_origin_router, prefix="/pedimentos", tags=["a76 / pedimentos / pedimento_rectification_origin"]) tags=["a76 / pedimentos / pedimento_config_parameters"],
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(
router.include_router(pedimentos_router, prefix="/pedimentos", tags=["a76 / pedimentos"]) 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 Routes for PedimentoConfigAdditional CRUD operations
""" """
from typing import Dict, Any
from fastapi import APIRouter, Depends, HTTPException, Query from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from core.database import get_core_db 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 ..services.pedimento_config_additional import PedimentoConfigAdditionalService
from ..dtos.pedimento_config_additional import ( from ..dtos.pedimento_config_additional import (
PedimentoConfigAdditionalCreate, PedimentoConfigAdditionalCreate,
PedimentoConfigAdditionalUpdate, PedimentoConfigAdditionalUpdate,
PedimentoConfigAdditionalResponse PedimentoConfigAdditionalResponse,
) )
@@ -22,11 +24,14 @@ async def get_config_additional(
pedimento_id: int, pedimento_id: int,
company_id: int = Query(..., description="Company ID"), 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),
): ):
"""Get config additional by pedimento ID""" """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: if not config:
raise HTTPException(status_code=404, detail="Config additional not found") raise HTTPException(status_code=404, detail="Config additional not found")
@@ -39,9 +44,10 @@ async def create_config_additional(
data: PedimentoConfigAdditionalCreate, data: PedimentoConfigAdditionalCreate,
company_id: int = Query(..., description="Company ID"), 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 additional""" """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 # Ensure pedimento_id and company_id match
if data.pedimento_id != pedimento_id: if data.pedimento_id != pedimento_id:
@@ -57,11 +63,14 @@ async def update_config_additional(
data: PedimentoConfigAdditionalUpdate, data: PedimentoConfigAdditionalUpdate,
company_id: int = Query(..., description="Company ID"), 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),
): ):
"""Update config additional""" """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: if not config:
raise HTTPException(status_code=404, detail="Config additional not found") raise HTTPException(status_code=404, detail="Config additional not found")
@@ -73,11 +82,14 @@ async def delete_config_additional(
pedimento_id: int, pedimento_id: int,
company_id: int = Query(..., description="Company ID"), 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),
): ):
"""Delete config additional""" """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: if not success:
raise HTTPException(status_code=404, detail="Config additional not found") raise HTTPException(status_code=404, detail="Config additional not found")

View File

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

View File

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

View File

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

View File

@@ -1,16 +1,20 @@
""" """
Routes for PedimentoConfigUpdateRectification CRUD operations Routes for PedimentoConfigUpdateRectification CRUD operations
""" """
from typing import Dict, Any
from fastapi import APIRouter, Depends, HTTPException, Query from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from core.database import get_core_db 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 ( from ..dtos.pedimento_config_update_rectification import (
PedimentoConfigUpdateRectificationCreate, PedimentoConfigUpdateRectificationCreate,
PedimentoConfigUpdateRectificationUpdate, PedimentoConfigUpdateRectificationUpdate,
PedimentoConfigUpdateRectificationResponse PedimentoConfigUpdateRectificationResponse,
) )
@@ -22,32 +26,42 @@ async def get_config_update_rectification(
pedimento_id: int, pedimento_id: int,
company_id: int = Query(..., description="Company ID"), 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),
): ):
"""Get config update rectification by pedimento ID""" """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: 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 return config
@router.post("/", response_model=PedimentoConfigUpdateRectificationResponse, status_code=201) @router.post(
"/", response_model=PedimentoConfigUpdateRectificationResponse, status_code=201
)
async def create_config_update_rectification( async def create_config_update_rectification(
pedimento_id: int, pedimento_id: int,
data: PedimentoConfigUpdateRectificationCreate, data: PedimentoConfigUpdateRectificationCreate,
company_id: int = Query(..., description="Company ID"), 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 update rectification""" """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 # Ensure pedimento_id matches
if data.pedimento_id != pedimento_id: if data.pedimento_id != pedimento_id:
raise HTTPException(status_code=400, detail="Pedimento ID mismatch") 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 return config
@@ -57,13 +71,18 @@ async def update_config_update_rectification(
data: PedimentoConfigUpdateRectificationUpdate, data: PedimentoConfigUpdateRectificationUpdate,
company_id: int = Query(..., description="Company ID"), 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),
): ):
"""Update config update rectification""" """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: 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 return config
@@ -73,12 +92,17 @@ async def delete_config_update_rectification(
pedimento_id: int, pedimento_id: int,
company_id: int = Query(..., description="Company ID"), 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),
): ):
"""Delete config update rectification""" """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: 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 return None

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,16 +1,20 @@
""" """
Routes for PedimentoRectificationDestination CRUD operations Routes for PedimentoRectificationDestination CRUD operations
""" """
from typing import Dict, Any
from fastapi import APIRouter, Depends, HTTPException, Query from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from core.database import get_core_db 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 ( from ..dtos.pedimento_rectification_destination import (
PedimentoRectificationDestinationCreate, PedimentoRectificationDestinationCreate,
PedimentoRectificationDestinationUpdate, PedimentoRectificationDestinationUpdate,
PedimentoRectificationDestinationResponse PedimentoRectificationDestinationResponse,
) )
@@ -22,32 +26,42 @@ async def get_rectification_destination(
pedimento_id: int, pedimento_id: int,
company_id: int = Query(..., description="Company ID"), 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),
): ):
"""Get rectification destination by pedimento ID""" """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: 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 return rectification
@router.post("/", response_model=PedimentoRectificationDestinationResponse, status_code=201) @router.post(
"/", response_model=PedimentoRectificationDestinationResponse, status_code=201
)
async def create_rectification_destination( async def create_rectification_destination(
pedimento_id: int, pedimento_id: int,
data: PedimentoRectificationDestinationCreate, data: PedimentoRectificationDestinationCreate,
company_id: int = Query(..., description="Company ID"), 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 rectification destination""" """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 # Ensure pedimento_id matches
if data.pedimento_id != pedimento_id: if data.pedimento_id != pedimento_id:
raise HTTPException(status_code=400, detail="Pedimento ID mismatch") 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 return rectification
@@ -57,13 +71,18 @@ async def update_rectification_destination(
data: PedimentoRectificationDestinationUpdate, data: PedimentoRectificationDestinationUpdate,
company_id: int = Query(..., description="Company ID"), 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),
): ):
"""Update rectification destination""" """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: 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 return rectification
@@ -73,12 +92,17 @@ async def delete_rectification_destination(
pedimento_id: int, pedimento_id: int,
company_id: int = Query(..., description="Company ID"), 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),
): ):
"""Delete rectification destination""" """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: 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 return None

View File

@@ -1,16 +1,20 @@
""" """
Routes for PedimentoRectificationOrigin CRUD operations Routes for PedimentoRectificationOrigin CRUD operations
""" """
from typing import Dict, Any
from fastapi import APIRouter, Depends, HTTPException, Query from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from core.database import get_core_db 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 ( from ..dtos.pedimento_rectification_origin import (
PedimentoRectificationOriginCreate, PedimentoRectificationOriginCreate,
PedimentoRectificationOriginUpdate, PedimentoRectificationOriginUpdate,
PedimentoRectificationOriginResponse PedimentoRectificationOriginResponse,
) )
@@ -22,11 +26,14 @@ async def get_rectification_origin(
pedimento_id: int, pedimento_id: int,
company_id: int = Query(..., description="Company ID"), 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),
): ):
"""Get rectification origin by pedimento ID""" """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: if not rectification:
raise HTTPException(status_code=404, detail="Rectification origin not found") raise HTTPException(status_code=404, detail="Rectification origin not found")
@@ -39,15 +46,18 @@ async def create_rectification_origin(
data: PedimentoRectificationOriginCreate, data: PedimentoRectificationOriginCreate,
company_id: int = Query(..., description="Company ID"), 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 rectification origin""" """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 # Ensure pedimento_id matches
if data.pedimento_id != pedimento_id: if data.pedimento_id != pedimento_id:
raise HTTPException(status_code=400, detail="Pedimento ID mismatch") 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 return rectification
@@ -57,11 +67,14 @@ async def update_rectification_origin(
data: PedimentoRectificationOriginUpdate, data: PedimentoRectificationOriginUpdate,
company_id: int = Query(..., description="Company ID"), 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),
): ):
"""Update rectification origin""" """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: if not rectification:
raise HTTPException(status_code=404, detail="Rectification origin not found") raise HTTPException(status_code=404, detail="Rectification origin not found")
@@ -73,11 +86,14 @@ async def delete_rectification_origin(
pedimento_id: int, pedimento_id: int,
company_id: int = Query(..., description="Company ID"), 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),
): ):
"""Delete rectification origin""" """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: if not success:
raise HTTPException(status_code=404, detail="Rectification origin not found") raise HTTPException(status_code=404, detail="Rectification origin not found")

View File

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