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()
@@ -54,7 +56,7 @@ if os.environ.get("ALEMBIC_DEBUG"):
debug_url = before + "@" + after debug_url = before + "@" + after
except Exception: except Exception:
debug_url = "postgresql://***:***@***" debug_url = "postgresql://***:***@***"
logger.error("Error al ocultar la contraseña en la URL para debug.") logger.error("Error al ocultar la contraseña en la URL para debug.")
config.set_main_option("sqlalchemy.url", database_url) config.set_main_option("sqlalchemy.url", database_url)
@@ -77,8 +79,9 @@ 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):
# Importar archivos models.py directos # Importar archivos models.py directos
if "models.py" in files: if "models.py" in files:
@@ -90,7 +93,7 @@ def import_models_from_dir(dir_path: str):
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)
# Importar todos los archivos .py en directorios llamados "models" # Importar todos los archivos .py en directorios llamados "models"
if os.path.basename(root) == "models": if os.path.basename(root) == "models":
for file in files: for file in files:
@@ -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()
@@ -159,4 +161,4 @@ def run_migrations_online() -> None:
if context.is_offline_mode(): if context.is_offline_mode():
run_migrations_offline() run_migrations_offline()
else: else:
run_migrations_online() run_migrations_online()

View File

@@ -1,10 +1,11 @@
"""create material_types table """create material_types table
Revision ID: 531bf8cdae06 Revision ID: 531bf8cdae06
Revises: 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', )
sa.Column('id', sa.Integer(), nullable=False), op.create_table(
sa.Column('pedimento_code', sa.String(length=3), nullable=False), "valuation_methods",
sa.Column('regimen_code', sa.String(length=3), nullable=False), sa.Column("key", sa.String(length=2), nullable=False),
sa.Column('type_code', sa.String(length=1), nullable=True), sa.Column("description", sa.String(length=200), nullable=False),
sa.ForeignKeyConstraint(['pedimento_code'], ['public.pedimento_codes.code'], name='fk_codeped'), sa.PrimaryKeyConstraint("key", name="valuation_methods_pkey"),
sa.ForeignKeyConstraint(['regimen_code'], ['public.pedimento_regimens.code'], name='fk_regimenped'), schema="public",
sa.PrimaryKeyConstraint('id', name='clave_pedimento_regimens_pkey'), )
schema='public' op.create_table(
"code_pedimento_regimens",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("pedimento_code", sa.String(length=3), nullable=False),
sa.Column("regimen_code", sa.String(length=3), nullable=False),
sa.Column("type_code", sa.String(length=1), nullable=True),
sa.ForeignKeyConstraint(
["pedimento_code"], ["public.pedimento_codes.code"], name="fk_codeped"
),
sa.ForeignKeyConstraint(
["regimen_code"], ["public.pedimento_regimens.code"], name="fk_regimenped"
),
sa.PrimaryKeyConstraint("id", name="clave_pedimento_regimens_pkey"),
schema="public",
) )
# ### end Alembic commands ### # ### 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,152 +5,293 @@ 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])
op.execute(f""" values_pr = ", ".join(
[
f"('{code}', '{desc.replace(chr(39), chr(39)*2)}')"
for code, desc in pedimento_regimens_seed
]
)
op.execute(
f"""
INSERT INTO public.pedimento_regimens (code, description) VALUES 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])
op.execute(f""" values_cpr = ", ".join(
[
f"('{ped_code}', '{reg_code}', '{type_code}')"
for ped_code, reg_code, type_code in code_pedimento_regimens_seed
]
)
op.execute(
f"""
INSERT INTO public.code_pedimento_regimens (pedimento_code, regimen_code, type_code) VALUES 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])
op.execute(f""" values_c = ", ".join(
[
f"('{key}', '{desc.replace(chr(39), chr(39)*2)}')"
for key, desc in container_types_seed
]
)
op.execute(
f"""
INSERT INTO public.containers (key, description) VALUES 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])
op.execute(f""" values_country = ", ".join(
[
f"('{m3_key}', '{mex_key}', '{ame_key}', '{desc_es.replace(chr(39), chr(39)*2)}', '{desc_en.replace(chr(39), chr(39)*2)}')"
for m3_key, mex_key, ame_key, desc_es, desc_en in countries_seed
]
)
op.execute(
f"""
INSERT INTO public.countries (m3_key, mex_key, ame_key, description_es, description_en) VALUES 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])
op.execute(f""" values_ct = ", ".join(
[
f"('{code}', '{currency_name.replace(chr(39), chr(39)*2)}', '{country_desc.replace(chr(39), chr(39)*2)}')"
for code, currency_name, country_desc in currency_types_seed
]
)
op.execute(
f"""
INSERT INTO public.currency_types (code, currency_name, country_description) VALUES 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])
op.execute(f""" values_cs = ", ".join(
[
f"('{code}', '{name.replace(chr(39), chr(39)*2)}')"
for code, name in customs_sections_seed
]
)
op.execute(
f"""
INSERT INTO public.customs_sections (customs_code, section_name) VALUES 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])
op.execute(f""" values_cw = ", ".join(
[
f"('{key}', '{customs.replace(chr(39), chr(39)*2)}', '{fiscalized_warehouse.replace(chr(39), chr(39)*2)}')"
for key, customs, fiscalized_warehouse in customs_warehouses_seed
]
)
op.execute(
f"""
INSERT INTO public.customs_warehouses (key, customs, fiscalized_warehouse) 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])
op.execute(f""" values_incoterms = ", ".join(
[
f"('{code}', '{desc_es.replace(chr(39), chr(39)*2)}', '{desc_en.replace(chr(39), chr(39)*2)}')"
for code, desc_es, desc_en in incoterms_seed
]
)
op.execute(
f"""
INSERT INTO public.incoterms (code, description_es, description_en) VALUES 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])
op.execute(f""" values_it = ", ".join(
[
f"('{key}', '{desc.replace(chr(39), chr(39)*2)}', '{note.replace(chr(39), chr(39)*2)}', '{type.replace(chr(39), chr(39)*2)}')"
for key, desc, note, type in invoice_types_seed
]
)
op.execute(
f"""
INSERT INTO public.invoice_types (key, description, note, type) VALUES 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])
op.execute(f""" values_mt = ", ".join(
[
f"('{key}', '{desc.replace(chr(39), chr(39)*2)}', '{category.replace(chr(39), chr(39)*2)}')"
for key, desc, category in material_types_seed
]
)
op.execute(
f"""
INSERT INTO public.material_types (key, description, type) VALUES 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])
op.execute(f""" values_sectors = ", ".join(
[
f"('{key}', '{desc.replace(chr(39), chr(39)*2)}', '{authorized}')"
for key, desc, authorized in sectors_seed
]
)
op.execute(
f"""
INSERT INTO public.sectors (key, description, authorized) VALUES 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])
op.execute(f""" values_tm = ", ".join(
[
f"('{key}', '{name.replace(chr(39), chr(39)*2)}')"
for key, name in transport_modes_seed
]
)
op.execute(
f"""
INSERT INTO public.transport_modes (key, name) VALUES 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])
op.execute(f""" values_tt = ", ".join(
[
f"('{code}', '{desc.replace(chr(39), chr(39)*2)}')"
for code, desc in transport_types_seed
]
)
op.execute(
f"""
INSERT INTO public.transport_types (transport_code, description) VALUES 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])
op.execute(f""" values_vm = ", ".join(
[
f"('{key}', '{desc.replace(chr(39), chr(39)*2)}')"
for key, desc in valuation_methods_seed
]
)
op.execute(
f"""
INSERT INTO public.valuation_methods (key, description) VALUES 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."""
op.execute("DELETE FROM public.valuation_methods;") op.execute("DELETE FROM public.valuation_methods;")
op.execute("DELETE FROM public.transport_types;") op.execute("DELETE FROM public.transport_types;")
op.execute("DELETE FROM public.transport_modes;") op.execute("DELETE FROM public.transport_modes;")

View File

@@ -1,32 +1,42 @@
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)
class_id: Mapped[int] = mapped_column(Integer, nullable=False) class_id: Mapped[int] = mapped_column(Integer, nullable=False)
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_type: Mapped[str] = mapped_column(String(6)) #TIPOFRACIMPO
export_tariff_code: Mapped[str] = mapped_column(String(10)) #FRACCIONEXPO
export_tariff_type: Mapped[str] = mapped_column(String(6)) #TIPOFRACEXPO
depreciation_rate: Mapped[Decimal] = mapped_column(Numeric(5, 2)) #TASADEPRECIA
fda_code: Mapped[str] = mapped_column(String(20)) #FDA
eccn_code: Mapped[str] = mapped_column(String(20)) #ECCN
class_enabled: Mapped[bool] = mapped_column(Boolean) #HABILITADESHABILITACLASE
import_tariff_code: Mapped[str] = mapped_column(String(10)) # FRACCIONIMPO
import_tariff_type: Mapped[str] = mapped_column(String(6)) # TIPOFRACIMPO
export_tariff_code: Mapped[str] = mapped_column(String(10)) # FRACCIONEXPO
export_tariff_type: Mapped[str] = mapped_column(String(6)) # TIPOFRACEXPO
depreciation_rate: Mapped[Decimal] = mapped_column(Numeric(5, 2)) # TASADEPRECIA
fda_code: Mapped[str] = mapped_column(String(20)) # FDA
eccn_code: Mapped[str] = mapped_column(String(20)) # ECCN
class_enabled: Mapped[bool] = mapped_column(Boolean) # HABILITADESHABILITACLASE

View File

@@ -1,21 +1,34 @@
from sqlalchemy import ForeignKeyConstraint, Integer, PrimaryKeyConstraint, String, UniqueConstraint from sqlalchemy import (
ForeignKeyConstraint,
Integer,
PrimaryKeyConstraint,
String,
UniqueConstraint,
)
from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy.orm 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,58 +1,63 @@
""" """
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")
class Config: class Config:
json_schema_extra = { json_schema_extra = {
"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"
expires_in: int expires_in: int
class Config: class Config:
json_schema_extra = { json_schema_extra = {
"example": { "example": {
"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
preferred_username: Optional[str] = None preferred_username: Optional[str] = None
tenant_id: Optional[int] = None tenant_id: Optional[int] = None
roles: list[str] = [] roles: list[str] = []
class Config: class Config:
json_schema_extra = { json_schema_extra = {
"example": { "example": {
@@ -61,25 +66,29 @@ 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")
last_name: str = Field(..., min_length=2, max_length=50, description="Apellido") last_name: str = Field(..., min_length=2, max_length=50, description="Apellido")
tenant_slug: str = Field(..., description="Slug del tenant") tenant_slug: str = Field(..., description="Slug del tenant")
class Config: class Config:
json_schema_extra = { json_schema_extra = {
"example": { "example": {
@@ -88,54 +97,57 @@ 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
message: str message: str
class Config: class Config:
json_schema_extra = { json_schema_extra = {
"example": { "example": {
"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)")
class Config: class Config:
json_schema_extra = { json_schema_extra = {
"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")
class Config: class Config:
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,12 +27,11 @@ 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
El usuario debe proporcionar: El usuario debe proporcionar:
- username: Nombre de usuario único - username: Nombre de usuario único
- email: Email único - email: Email único
@@ -39,7 +39,7 @@ async def register(
- first_name: Nombre - first_name: Nombre
- last_name: Apellido - last_name: Apellido
- tenant_slug: Slug del tenant al que pertenece - tenant_slug: Slug del tenant al que pertenece
El usuario se crea automáticamente en Keycloak con: El usuario se crea automáticamente en Keycloak con:
- Cuenta habilitada - Cuenta habilitada
- Rol 'user' asignado por defecto - Rol 'user' asignado por defecto
@@ -50,13 +50,10 @@ 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
El usuario debe proporcionar: El usuario debe proporcionar:
- username: Usuario o email - username: Usuario o email
- password: Contraseña - password: Contraseña
@@ -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,15 +101,14 @@ 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
Este endpoint es útil cuando el frontend usa el flujo de autorización Este endpoint es útil cuando el frontend usa el flujo de autorización
con proveedores externos (Microsoft, Google, etc.) a través de Keycloak. con proveedores externos (Microsoft, Google, etc.) a través de Keycloak.
El código se obtiene después de que el usuario se autentica con el proveedor El código se obtiene después de que el usuario se autentica con el proveedor
externo y Keycloak lo redirige al frontend con el código en los query params. externo y Keycloak lo redirige al frontend con el código en los query params.
""" """
@@ -125,15 +120,15 @@ 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
Este endpoint se llama desde el frontend después de una autenticación Este endpoint se llama desde el frontend después de una autenticación
SSO exitosa para establecer las cookies de sesión necesarias para SSO exitosa para establecer las cookies de sesión necesarias para
la validación server-side en los layouts protegidos. la validación server-side en los layouts protegidos.
Las cookies se configuran como: Las cookies se configuran como:
- HttpOnly: No accesibles desde JavaScript (mayor seguridad) - HttpOnly: No accesibles desde JavaScript (mayor seguridad)
- Secure: Solo se envían por HTTPS (en producción) - Secure: Solo se envían por HTTPS (en producción)
@@ -145,7 +140,7 @@ async def set_cookie(
try: try:
# Validar el access token # Validar el access token
user_info = service.get_user_info(cookie_data.access_token) user_info = service.get_user_info(cookie_data.access_token)
# Establecer las cookies # Establecer las cookies
# Access token cookie # Access token cookie
response.set_cookie( response.set_cookie(
@@ -155,9 +150,9 @@ 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
response.set_cookie( response.set_cookie(
key="refresh_token", key="refresh_token",
@@ -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__)
@@ -23,26 +24,26 @@ logger = logging.getLogger(__name__)
class AuthService: class AuthService:
"""Servicio de autenticación""" """Servicio de autenticación"""
def __init__(self, db: Session): def __init__(self, db: Session):
self.db = db self.db = db
self.keycloak_openid = KeycloakOpenID( self.keycloak_openid = KeycloakOpenID(
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:
""" """
Autentica usuario y obtiene tokens Autentica usuario y obtiene tokens
Args: Args:
login_data: Credenciales de login login_data: Credenciales de login
Returns: Returns:
TokenResponseDTO con access_token y refresh_token TokenResponseDTO con access_token y refresh_token
Raises: Raises:
HTTPException: Si las credenciales son inválidas HTTPException: Si las credenciales son inválidas
""" """
@@ -50,47 +51,50 @@ class AuthService:
# 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
from api.v1.modules.a76.user_tenant.service import UserTenantService from api.v1.modules.a76.user_tenant.service import UserTenantService
tenant_service = TenantService(self.db) tenant_service = TenantService(self.db)
user_tenant_service = UserTenantService(self.db) user_tenant_service = UserTenantService(self.db)
tenant = tenant_service.get_tenant_by_slug(login_data.tenant_slug) tenant = tenant_service.get_tenant_by_slug(login_data.tenant_slug)
if not tenant: if not tenant:
raise HTTPException(status_code=404, detail="Tenant not found") raise HTTPException(status_code=404, detail="Tenant not found")
if not tenant.is_active: if not tenant.is_active:
raise HTTPException(status_code=403, detail="Tenant is not active") raise HTTPException(status_code=403, detail="Tenant is not active")
# Crear nueva instancia de KeycloakOpenID con el realm del tenant # Crear nueva instancia de KeycloakOpenID con el realm del tenant
keycloak_client = KeycloakOpenID( keycloak_client = KeycloakOpenID(
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
user_info = keycloak_client.userinfo(token_response["access_token"]) user_info = keycloak_client.userinfo(token_response["access_token"])
user_id = user_info.get("sub") user_id = user_info.get("sub")
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(
raise HTTPException( f"User {user_id} tried to access tenant {tenant.id} without permission"
status_code=403,
detail="You don't have access to this tenant"
) )
raise HTTPException(
status_code=403, detail="You don't have access to this tenant"
)
# Actualizar el tenant_id del usuario en Keycloak basado en el slug usado # Actualizar el tenant_id del usuario en Keycloak basado en el slug usado
try: try:
# Crear instancia de KeycloakAdmin para actualizar atributos # Crear instancia de KeycloakAdmin para actualizar atributos
@@ -100,19 +104,19 @@ 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
current_user = keycloak_admin.get_user(user_id) current_user = keycloak_admin.get_user(user_id)
# Obtener los atributos actuales o crear un dict vacío # Obtener los atributos actuales o crear un dict vacío
current_attributes = current_user.get("attributes", {}) current_attributes = current_user.get("attributes", {})
# Actualizar solo los atributos de tenant # Actualizar solo los atributos de tenant
current_attributes["tenant_id"] = [str(tenant.id)] current_attributes["tenant_id"] = [str(tenant.id)]
current_attributes["tenant_slug"] = [tenant.slug] current_attributes["tenant_slug"] = [tenant.slug]
# Actualizar el usuario enviando TODOS los campos para evitar que se borren # Actualizar el usuario enviando TODOS los campos para evitar que se borren
update_payload = { update_payload = {
"email": current_user.get("email"), "email": current_user.get("email"),
@@ -120,23 +124,25 @@ 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
logger.warning(f"Could not update tenant_id attribute: {str(e)}") logger.warning(f"Could not update tenant_id attribute: {str(e)}")
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="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"Keycloak authentication failed: {str(e)}") logger.warning(f"Keycloak authentication failed: {str(e)}")
raise HTTPException(status_code=401, detail="Invalid credentials") raise HTTPException(status_code=401, detail="Invalid credentials")
@@ -145,14 +151,14 @@ class AuthService:
except Exception as e: except Exception as e:
logger.error(f"Login error: {str(e)}") logger.error(f"Login error: {str(e)}")
raise HTTPException(status_code=500, detail="Authentication error") raise HTTPException(status_code=500, detail="Authentication error")
def refresh_token(self, refresh_data: RefreshTokenRequestDTO) -> TokenResponseDTO: def refresh_token(self, refresh_data: RefreshTokenRequestDTO) -> TokenResponseDTO:
""" """
Refresca el access token usando refresh token Refresca el access token usando refresh token
Args: Args:
refresh_data: Refresh token refresh_data: Refresh token
Returns: Returns:
TokenResponseDTO con nuevos tokens TokenResponseDTO con nuevos tokens
""" """
@@ -160,67 +166,69 @@ class AuthService:
token_response = self.keycloak_openid.refresh_token( token_response = self.keycloak_openid.refresh_token(
refresh_data.refresh_token refresh_data.refresh_token
) )
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="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")
def get_user_info(self, access_token: str) -> UserInfoResponseDTO: def get_user_info(self, access_token: str) -> UserInfoResponseDTO:
""" """
Obtiene información del usuario desde el token Obtiene información del usuario desde el token
Args: Args:
access_token: Access token JWT access_token: Access token JWT
Returns: Returns:
UserInfoResponseDTO con información del usuario UserInfoResponseDTO con información del usuario
""" """
try: try:
user_info = self.keycloak_openid.userinfo(access_token) user_info = self.keycloak_openid.userinfo(access_token)
# Extraer roles # Extraer roles
roles = [] roles = []
if "realm_access" in user_info: if "realm_access" in user_info:
roles = user_info["realm_access"].get("roles", []) roles = user_info["realm_access"].get("roles", [])
# Extraer tenant_id si está presente # Extraer tenant_id si está presente
tenant_id = user_info.get("tenant_id") tenant_id = user_info.get("tenant_id")
if not tenant_id and "attributes" in user_info: if not tenant_id and "attributes" in user_info:
tenant_id = user_info["attributes"].get("tenant_id") tenant_id = user_info["attributes"].get("tenant_id")
return UserInfoResponseDTO( return UserInfoResponseDTO(
sub=user_info.get("sub"), sub=user_info.get("sub"),
email=user_info.get("email"), email=user_info.get("email"),
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:
logger.warning(f"Get user info failed: {str(e)}") logger.warning(f"Get user info failed: {str(e)}")
raise HTTPException(status_code=401, detail="Invalid token") raise HTTPException(status_code=401, detail="Invalid token")
except Exception as e: except Exception as e:
logger.error(f"Get user info error: {str(e)}") logger.error(f"Get user info error: {str(e)}")
raise HTTPException(status_code=500, detail="Error retrieving user info") raise HTTPException(status_code=500, detail="Error retrieving user info")
def logout(self, logout_data: LogoutRequestDTO) -> dict: def logout(self, logout_data: LogoutRequestDTO) -> dict:
""" """
Cierra sesión invalidando el refresh token Cierra sesión invalidando el refresh token
Args: Args:
logout_data: Refresh token a invalidar logout_data: Refresh token a invalidar
Returns: Returns:
Dict con mensaje de éxito Dict con mensaje de éxito
""" """
@@ -228,7 +236,7 @@ class AuthService:
self.keycloak_openid.logout(logout_data.refresh_token) self.keycloak_openid.logout(logout_data.refresh_token)
logger.info("User logged out successfully") logger.info("User logged out successfully")
return {"message": "Logged out successfully"} return {"message": "Logged out successfully"}
except KeycloakError as e: except KeycloakError as e:
logger.warning(f"Logout failed: {str(e)}") logger.warning(f"Logout failed: {str(e)}")
# No lanzamos error aquí, el logout puede fallar si el token ya expiró # No lanzamos error aquí, el logout puede fallar si el token ya expiró
@@ -236,32 +244,33 @@ class AuthService:
except Exception as e: except Exception as e:
logger.error(f"Logout error: {str(e)}") logger.error(f"Logout error: {str(e)}")
raise HTTPException(status_code=500, detail="Logout error") raise HTTPException(status_code=500, detail="Logout error")
def register(self, register_data: RegisterRequestDTO) -> RegisterResponseDTO: def register(self, register_data: RegisterRequestDTO) -> RegisterResponseDTO:
""" """
Registra un nuevo usuario en Keycloak Registra un nuevo usuario en Keycloak
Args: Args:
register_data: Datos del usuario a registrar register_data: Datos del usuario a registrar
Returns: Returns:
RegisterResponseDTO con información del usuario creado RegisterResponseDTO con información del usuario creado
Raises: Raises:
HTTPException: Si el registro falla HTTPException: Si el registro falla
""" """
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)
if not tenant: if not tenant:
raise HTTPException(status_code=404, detail="Tenant not found") raise HTTPException(status_code=404, detail="Tenant not found")
if not tenant.is_active: if not tenant.is_active:
raise HTTPException(status_code=403, detail="Tenant is not active") raise HTTPException(status_code=403, detail="Tenant is not active")
# Crear instancia de KeycloakAdmin para gestión de usuarios # Crear instancia de KeycloakAdmin para gestión de usuarios
keycloak_admin = KeycloakAdmin( keycloak_admin = KeycloakAdmin(
server_url=settings.KEYCLOAK_SERVER_URL, server_url=settings.KEYCLOAK_SERVER_URL,
@@ -269,9 +278,9 @@ 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
user_data = { user_data = {
"username": register_data.username, "username": register_data.username,
@@ -280,20 +289,19 @@ 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
user_id = keycloak_admin.create_user(user_data) user_id = keycloak_admin.create_user(user_data)
# Asignar rol por defecto (user) - opcional, solo si existe # Asignar rol por defecto (user) - opcional, solo si existe
try: try:
user_role = keycloak_admin.get_realm_role("user") user_role = keycloak_admin.get_realm_role("user")
@@ -303,15 +311,16 @@ class AuthService:
except KeycloakError as e: except KeycloakError as e:
# El rol 'user' no existe, no es un error crítico # El rol 'user' no existe, no es un error crítico
logger.warning(f"Could not assign 'user' role: {str(e)}") logger.warning(f"Could not assign 'user' role: {str(e)}")
# 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,106 +332,116 @@ 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:
error_message = str(e) error_message = str(e)
logger.warning(f"Keycloak registration failed: {error_message}") logger.warning(f"Keycloak registration failed: {error_message}")
# 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:
raise HTTPException(status_code=500, detail="Registration error") raise HTTPException(status_code=500, detail="Registration error")
except HTTPException: except HTTPException:
raise raise
except Exception as e: except Exception as e:
logger.error(f"Registration error: {str(e)}") logger.error(f"Registration error: {str(e)}")
raise HTTPException(status_code=500, detail="Registration error") raise HTTPException(status_code=500, detail="Registration error")
def exchange_code(self, exchange_data) -> TokenResponseDTO: def exchange_code(self, exchange_data) -> TokenResponseDTO:
""" """
Intercambia un authorization code por tokens Intercambia un authorization code por tokens
Este método se usa cuando el frontend recibe un código de autorización Este método se usa cuando el frontend recibe un código de autorización
después de un login con proveedor externo (Microsoft, Google, etc.) después de un login con proveedor externo (Microsoft, Google, etc.)
a través de Keycloak. a través de Keycloak.
Args: Args:
exchange_data: Datos del código y redirect_uri exchange_data: Datos del código y redirect_uri
Returns: Returns:
TokenResponseDTO con access_token y refresh_token TokenResponseDTO con access_token y refresh_token
Raises: Raises:
HTTPException: Si el código es inválido o expiró HTTPException: Si el código es inválido o expiró
""" """
try: try:
# Importar el DTO aquí para evitar referencias circulares # Importar el DTO aquí para evitar referencias circulares
from .dto import ExchangeCodeRequestDTO from .dto import ExchangeCodeRequestDTO
# 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")
# Si se proporciona tenant_slug, podríamos validar que el usuario pertenece a ese tenant # Si se proporciona tenant_slug, podríamos validar que el usuario pertenece a ese tenant
# 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)
if not tenant: if not tenant:
raise HTTPException(status_code=404, detail="Tenant not found") raise HTTPException(status_code=404, detail="Tenant not found")
if not tenant.is_active: if not tenant.is_active:
raise HTTPException(status_code=403, detail="Tenant is not active") raise HTTPException(status_code=403, detail="Tenant is not active")
# Opcional: Verificar que el usuario pertenece al tenant # Opcional: Verificar que el usuario pertenece al tenant
# 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:
error_message = str(e) error_message = str(e)
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")
except HTTPException: except HTTPException:
raise raise
except Exception as e: except Exception as e:

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,54 +24,78 @@ 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)
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)
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
@@ -9,28 +10,33 @@ 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
from .service import ClassService from .service import ClassService
from .dto import ( from .dto import (
ClassCreateDTO, ClassCreateDTO,
ClassUpdateDTO, ClassUpdateDTO,
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)
@@ -154,16 +163,17 @@ async def get_class(
class_obj = service.get_class(client_id, class_code) class_obj = service.get_class(client_id, class_code)
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
@@ -186,8 +197,8 @@ async def update_class(
class_obj = service.update_class(client_id, class_code, class_data) class_obj = service.update_class(client_id, class_code, class_data)
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,18 +208,18 @@ 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
Note: This will completely remove the class from the system. Note: This will completely remove the class from the system.
""" """
service = ClassService(db) service = ClassService(db)
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
@@ -227,17 +238,17 @@ async def get_class_basic_info(
class_obj = service.get_class(client_id, class_code) class_obj = service.get_class(client_id, class_code)
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(
client_id=class_obj.client_id, client_id=class_obj.client_id,
class_code=class_obj.class_code, class_code=class_obj.class_code,
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.)
@@ -255,10 +266,10 @@ async def get_class_tariff_info(
class_obj = service.get_class(client_id, class_code) class_obj = service.get_class(client_id, class_code)
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 {
"client_id": class_obj.client_id, "client_id": class_obj.client_id,
"class_code": class_obj.class_code, "class_code": class_obj.class_code,
@@ -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
@@ -10,12 +11,12 @@ import logging
from .models import Class from .models import Class
from .dto import ( from .dto import (
ClassCreateDTO, ClassCreateDTO,
ClassUpdateDTO, ClassUpdateDTO,
ClassResponseDTO, ClassResponseDTO,
ClassBasicDTO, ClassBasicDTO,
ClassListDTO, ClassListDTO,
ClassSearchDTO ClassSearchDTO,
) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -23,38 +24,42 @@ logger = logging.getLogger(__name__)
class ClassService: class ClassService:
"""Servicio para gestión de clases SCAII y SCAF""" """Servicio para gestión de clases SCAII y SCAF"""
def __init__(self, db: Session): def __init__(self, db: Session):
self.db = db self.db = db
def create_class(self, class_data: ClassCreateDTO) -> ClassResponseDTO: def create_class(self, class_data: ClassCreateDTO) -> ClassResponseDTO:
""" """
Crea una nueva clase en el sistema Crea una nueva clase en el sistema
Args: Args:
class_data: Datos de la clase a crear class_data: Datos de la clase a crear
Returns: Returns:
ClassResponseDTO con información de la clase creada ClassResponseDTO con información de la clase creada
Raises: Raises:
HTTPException: Si la clase ya existe o error en la creación HTTPException: Si la clase ya existe o error en la creación
""" """
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
db_class = Class( db_class = Class(
client_id=class_data.client_id, client_id=class_data.client_id,
@@ -67,171 +72,181 @@ 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)
self.db.commit() self.db.commit()
self.db.refresh(db_class) self.db.refresh(db_class)
logger.info(f"Class created: {db_class.client_id}-{db_class.class_code}") logger.info(f"Class created: {db_class.client_id}-{db_class.class_code}")
return ClassResponseDTO.model_validate(db_class) return ClassResponseDTO.model_validate(db_class)
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:
self.db.rollback() self.db.rollback()
logger.error(f"Error creating class: {str(e)}") logger.error(f"Error creating class: {str(e)}")
raise HTTPException(status_code=500, detail="Error creating class") raise HTTPException(status_code=500, detail="Error creating class")
def get_class(self, client_id: int, class_code: str) -> Optional[ClassResponseDTO]: def get_class(self, client_id: int, class_code: str) -> Optional[ClassResponseDTO]:
""" """
Obtiene una clase por clave compuesta Obtiene una clase por clave compuesta
Args: Args:
client_id: Clave del cliente client_id: Clave del cliente
class_code: Código de clase class_code: Código de clase
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
return ClassResponseDTO.model_validate(class_obj) return ClassResponseDTO.model_validate(class_obj)
def list_classes( def list_classes(
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
Args: Args:
skip: Número de registros a omitir skip: Número de registros a omitir
limit: Número máximo de registros a retornar limit: Número máximo de registros a retornar
search_params: Parámetros de búsqueda search_params: Parámetros de búsqueda
Returns: Returns:
ClassListDTO con la lista paginada ClassListDTO con la lista paginada
""" """
query = self.db.query(Class) query = self.db.query(Class)
# Aplicar filtros si se proporcionan # Aplicar filtros si se proporcionan
if search_params: if search_params:
if search_params.client_id: if search_params.client_id:
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()
# Aplicar paginación # Aplicar paginación
classes = query.offset(skip).limit(limit).all() classes = query.offset(skip).limit(limit).all()
# Convertir a DTOs básicos # Convertir a DTOs básicos
class_dtos = [ClassBasicDTO.model_validate(class_obj) for class_obj in classes] class_dtos = [ClassBasicDTO.model_validate(class_obj) for class_obj in classes]
return ClassListDTO( return ClassListDTO(
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
Args: Args:
client_id: Clave del cliente client_id: Clave del cliente
class_code: Código de clase class_code: Código de clase
class_data: Datos a actualizar class_data: Datos a actualizar
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
try: try:
# Actualizar solo campos proporcionados # Actualizar solo campos proporcionados
update_data = class_data.model_dump(exclude_unset=True) update_data = class_data.model_dump(exclude_unset=True)
for field, value in update_data.items(): for field, value in update_data.items():
setattr(class_obj, field, value) setattr(class_obj, field, value)
self.db.commit() self.db.commit()
self.db.refresh(class_obj) self.db.refresh(class_obj)
logger.info(f"Class updated: {client_id}-{class_code}") logger.info(f"Class updated: {client_id}-{class_code}")
return ClassResponseDTO.model_validate(class_obj) return ClassResponseDTO.model_validate(class_obj)
except Exception as e: except Exception as e:
self.db.rollback() self.db.rollback()
logger.error(f"Error updating class {client_id}-{class_code}: {str(e)}") logger.error(f"Error updating class {client_id}-{class_code}: {str(e)}")
raise HTTPException(status_code=500, detail="Error updating class") raise HTTPException(status_code=500, detail="Error updating class")
def delete_class(self, client_id: int, class_code: str) -> bool: def delete_class(self, client_id: int, class_code: str) -> bool:
""" """
Elimina una clase Elimina una clase
Args: Args:
client_id: Clave del cliente client_id: Clave del cliente
class_code: Código de clase class_code: Código de clase
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
try: try:
self.db.delete(class_obj) self.db.delete(class_obj)
self.db.commit() self.db.commit()
@@ -241,54 +256,75 @@ class ClassService:
self.db.rollback() self.db.rollback()
logger.error(f"Error deleting class {client_id}-{class_code}: {str(e)}") logger.error(f"Error deleting class {client_id}-{class_code}: {str(e)}")
raise HTTPException(status_code=500, detail="Error deleting class") raise HTTPException(status_code=500, detail="Error deleting class")
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:
"""Obtiene estadísticas básicas de clases""" """Obtiene estadísticas básicas de clases"""
total_classes = self.db.query(Class).count() total_classes = self.db.query(Class).count()
# Contar por clientes # Contar por clientes
clients_count = self.db.query(Class.client_id).distinct().count() clients_count = self.db.query(Class.client_id).distinct().count()
# Contar por revisión física # Contar por revisión física
physical_review_stats = {} physical_review_stats = {}
for i in range(3): # Asumiendo valores 0, 1, 2 for i in range(3): # Asumiendo valores 0, 1, 2
count = self.db.query(Class).filter(Class.physical_review == i).count() count = self.db.query(Class).filter(Class.physical_review == i).count()
physical_review_stats[f"physical_review_{i}"] = count physical_review_stats[f"physical_review_{i}"] = count
# 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
@@ -130,7 +196,7 @@ class ClientProviderResponseDTO(BaseModel):
incoterm: Optional[str] = None incoterm: Optional[str] = None
is_national_provider: Optional[str] = None is_national_provider: Optional[str] = None
enabled_disabled: Optional[int] = None enabled_disabled: Optional[int] = None
# Nested DTOs # Nested DTOs
address: Optional[ClientProviderAddressDTO] = None address: Optional[ClientProviderAddressDTO] = None
programs: Optional[ClientProviderProgramsDTO] = None programs: Optional[ClientProviderProgramsDTO] = 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,21 +21,28 @@ 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
id: Mapped[int] = mapped_column(Integer, primary_key=True) id: Mapped[int] = mapped_column(Integer, primary_key=True)
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)
# 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))
@@ -40,31 +56,45 @@ class ClientProvider(Base):
position: Mapped[Optional[str]] = mapped_column(String(30)) position: Mapped[Optional[str]] = mapped_column(String(30))
incoterm: Mapped[Optional[str]] = mapped_column(String(19)) incoterm: Mapped[Optional[str]] = mapped_column(String(19))
is_national_provider: Mapped[Optional[str]] = mapped_column(String(2)) is_national_provider: Mapped[Optional[str]] = mapped_column(String(2))
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)
# Address information # Address information
municipality: Mapped[Optional[str]] = mapped_column(String(150)) municipality: Mapped[Optional[str]] = mapped_column(String(150))
streets: Mapped[Optional[str]] = mapped_column(String(100)) streets: Mapped[Optional[str]] = mapped_column(String(100))
@@ -80,7 +110,7 @@ class ClientProviderAddress(Base):
email: Mapped[Optional[str]] = mapped_column(String(100)) email: Mapped[Optional[str]] = mapped_column(String(100))
contact: Mapped[Optional[str]] = mapped_column(String(50)) contact: Mapped[Optional[str]] = mapped_column(String(50))
reference: Mapped[Optional[str]] = mapped_column(String(250)) reference: Mapped[Optional[str]] = mapped_column(String(250))
# Relationship # Relationship
client_provider: Mapped["ClientProvider"] = relationship(back_populates="address") client_provider: Mapped["ClientProvider"] = relationship(back_populates="address")
@@ -89,20 +119,30 @@ 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)
# Program information # Program information
program: Mapped[Optional[str]] = mapped_column(String(7)) program: Mapped[Optional[str]] = mapped_column(String(7))
program_number: Mapped[Optional[str]] = mapped_column(String(40)) program_number: Mapped[Optional[str]] = mapped_column(String(40))
@@ -124,8 +164,6 @@ class ClientProviderPrograms(Base):
subassembly_service: Mapped[Optional[int]] = mapped_column(SmallInteger) subassembly_service: Mapped[Optional[int]] = mapped_column(SmallInteger)
autse_dates: Mapped[Optional[int]] = mapped_column() autse_dates: Mapped[Optional[int]] = mapped_column()
autse_number: Mapped[Optional[str]] = mapped_column(String(300)) autse_number: Mapped[Optional[str]] = mapped_column(String(300))
# 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
@@ -9,21 +10,23 @@ 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
from .service import ClientProviderService from .service import ClientProviderService
from .dto import ( from .dto import (
ClientProviderCreateDTO, ClientProviderCreateDTO,
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,11 +193,11 @@ 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
Note: This will completely remove the client/provider and all related data. Note: This will completely remove the client/provider and all related data.
""" """
# Validate access to the tenant and company # Validate access to the tenant and company
@@ -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 { )
"client_id": client.client_id,
"address": client.address return {"client_id": client.client_id, "address": client.address}
}
@router.get("/{client_id}/programs", response_model=dict) @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 { )
"client_id": client.client_id,
"programs": client.programs return {"client_id": client.client_id, "programs": client.programs}
}
@router.get("/{client_id}/basic", response_model=ClientProviderBasicDTO) @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,14 +311,15 @@ 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,
name=client.name, name=client.name,
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_
@@ -10,13 +11,13 @@ import logging
from .models import ClientProvider, ClientProviderAddress, ClientProviderPrograms from .models import ClientProvider, ClientProviderAddress, ClientProviderPrograms
from .dto import ( from .dto import (
ClientProviderCreateDTO, ClientProviderCreateDTO,
ClientProviderUpdateDTO, ClientProviderUpdateDTO,
ClientProviderResponseDTO, ClientProviderResponseDTO,
ClientProviderBasicDTO, ClientProviderBasicDTO,
ClientProviderListDTO, ClientProviderListDTO,
ClientProviderAddressDTO, ClientProviderAddressDTO,
ClientProviderProgramsDTO ClientProviderProgramsDTO,
) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -24,29 +25,38 @@ logger = logging.getLogger(__name__)
class ClientProviderService: class ClientProviderService:
"""Servicio para gestión de clientes y proveedores""" """Servicio para gestión de clientes y proveedores"""
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
Args: Args:
client_data: Datos del cliente/proveedor a crear client_data: Datos del cliente/proveedor a crear
Returns: Returns:
ClientProviderResponseDTO con información del cliente/proveedor creado ClientProviderResponseDTO con información del cliente/proveedor creado
Raises: Raises:
HTTPException: Si el cliente ya existe o error en la creación HTTPException: Si el cliente ya existe o error en la creación
""" """
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(
client_id=client_data.client_id, client_id=client_data.client_id,
@@ -64,92 +74,106 @@ 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)
self.db.flush() # Para obtener el ID antes del commit self.db.flush() # Para obtener el ID antes del commit
# Crear dirección si se proporciona # Crear dirección si se proporciona
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)
# Crear programas si se proporciona # Crear programas si se proporciona
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
Args: Args:
client_id: ID del cliente/proveedor client_id: ID del cliente/proveedor
Returns: Returns:
ClientProviderResponseDTO o None si no existe ClientProviderResponseDTO o None si no existe
""" """
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
return ClientProviderResponseDTO.model_validate(client) return ClientProviderResponseDTO.model_validate(client)
def list_clients_providers( def list_clients_providers(
self, self,
skip: int = 0, skip: int = 0,
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
Args: Args:
skip: Número de registros a omitir skip: Número de registros a omitir
limit: Número máximo de registros a retornar limit: Número máximo de registros a retornar
search: Texto de búsqueda (nombre, RFC, ID) search: Texto de búsqueda (nombre, RFC, ID)
client_or_provider: Filtrar por tipo (C=Cliente, P=Proveedor) client_or_provider: Filtrar por tipo (C=Cliente, P=Proveedor)
enabled_only: Si True, solo retorna activos enabled_only: Si True, solo retorna activos
Returns: Returns:
ClientProviderListDTO con la lista paginada ClientProviderListDTO con la lista paginada
""" """
query = self.db.query(ClientProvider) query = self.db.query(ClientProvider)
# Aplicar filtros # Aplicar filtros
if search: if search:
search_pattern = f"%{search}%" search_pattern = f"%{search}%"
@@ -158,56 +182,72 @@ 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)
# Contar total # Contar total
total = query.count() total = query.count()
# Aplicar paginación # Aplicar paginación
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
Args: Args:
client_id: ID del cliente/proveedor a actualizar client_id: ID del cliente/proveedor a actualizar
client_data: Datos a actualizar client_data: Datos a actualizar
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,34 +277,40 @@ 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)
self.db.commit() self.db.commit()
logger.info(f"Client/Provider updated: {client_id}") logger.info(f"Client/Provider updated: {client_id}")
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 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:
""" """
Elimina un cliente/proveedor Elimina un cliente/proveedor
Args: Args:
client_id: ID del cliente/proveedor a eliminar client_id: ID del cliente/proveedor a eliminar
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
try: try:
self.db.delete(client) # Las relaciones se eliminan en cascada self.db.delete(client) # Las relaciones se eliminan en cascada
self.db.commit() self.db.commit()
@@ -269,41 +319,61 @@ 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
# Toggle status (1 = habilitado, 0 = deshabilitado) # Toggle status (1 = habilitado, 0 = deshabilitado)
client.enabled_disabled = 1 if client.enabled_disabled == 0 else 0 client.enabled_disabled = 1 if client.enabled_disabled == 0 else 0
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,22 +172,23 @@ 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
# Program information # Program information
program: Optional[str] = None program: Optional[str] = None
program_number: Optional[str] = None program_number: Optional[str] = None
prosec: Optional[int] = None prosec: Optional[int] = None
prosec_authorization: Optional[str] = None prosec_authorization: Optional[str] = None
# Identifiers # Identifiers
manufacturer_id: Optional[str] = None manufacturer_id: Optional[str] = None
broker_company: Optional[str] = None broker_company: Optional[str] = None
# Responsible person # Responsible person
responsible: Optional[str] = None responsible: Optional[str] = None
responsible_name: Optional[str] = None responsible_name: Optional[str] = None
@@ -128,18 +196,18 @@ class CompanyResponseDTO(BaseModel):
responsible_mother_last_name: Optional[str] = None responsible_mother_last_name: Optional[str] = None
responsible_rfc: Optional[str] = None responsible_rfc: Optional[str] = None
position: Optional[str] = None position: Optional[str] = None
# Configuration # Configuration
logo: Optional[str] = None logo: Optional[str] = None
has_express_line: Optional[bool] = None has_express_line: Optional[bool] = None
order_format_type: Optional[str] = None order_format_type: Optional[str] = None
previous_code: Optional[int] = None previous_code: Optional[int] = None
is_service_company: Optional[bool] = None is_service_company: Optional[bool] = None
# Client and subassembly # Client and subassembly
client_name: Optional[str] = None client_name: Optional[str] = None
subassembly_mode: Optional[str] = None subassembly_mode: Optional[str] = None
# Additional information # Additional information
curp: Optional[str] = None curp: Optional[str] = None
inter_db_name: Optional[str] = None inter_db_name: Optional[str] = None
@@ -147,11 +215,10 @@ class CompanyResponseDTO(BaseModel):
trusted_exporter_number: Optional[str] = None trusted_exporter_number: Optional[str] = None
prevalidator_key: Optional[str] = None prevalidator_key: Optional[str] = None
seventh_amendment: Optional[bool] = None seventh_amendment: Optional[bool] = None
# Timestamps # Timestamps
created_at: datetime created_at: datetime
updated_at: Optional[datetime] = None updated_at: Optional[datetime] = None
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,32 +24,35 @@ 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
id: Mapped[int] = mapped_column(Integer, primary_key=True) id: Mapped[int] = mapped_column(Integer, primary_key=True)
tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True) tenant_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
# Información básica de la empresa # Información básica de la empresa
name: Mapped[Optional[str]] = mapped_column(String(255)) name: Mapped[Optional[str]] = mapped_column(String(255))
rfc: Mapped[Optional[str]] = mapped_column(String(30)) rfc: Mapped[Optional[str]] = mapped_column(String(30))
main_activity: Mapped[Optional[str]] = mapped_column(String(255)) main_activity: Mapped[Optional[str]] = mapped_column(String(255))
# Información del programa # Información del programa
program: Mapped[Optional[str]] = mapped_column(String(10)) program: Mapped[Optional[str]] = mapped_column(String(10))
program_number: Mapped[Optional[str]] = mapped_column(String(40)) program_number: Mapped[Optional[str]] = mapped_column(String(40))
prosec: Mapped[Optional[int]] = mapped_column(SmallInteger) prosec: Mapped[Optional[int]] = mapped_column(SmallInteger)
prosec_authorization: Mapped[Optional[str]] = mapped_column(String(20)) prosec_authorization: Mapped[Optional[str]] = mapped_column(String(20))
# Identificadores # Identificadores
manufacturer_id: Mapped[Optional[str]] = mapped_column(String(25)) manufacturer_id: Mapped[Optional[str]] = mapped_column(String(25))
broker_company: Mapped[Optional[str]] = mapped_column(String(10)) broker_company: Mapped[Optional[str]] = mapped_column(String(10))
# Responsable # Responsable
responsible: Mapped[Optional[str]] = mapped_column(String(80)) responsible: Mapped[Optional[str]] = mapped_column(String(80))
responsible_name: Mapped[Optional[str]] = mapped_column(String(20)) responsible_name: Mapped[Optional[str]] = mapped_column(String(20))
@@ -46,29 +60,33 @@ class Company(Base):
responsible_mother_last_name: Mapped[Optional[str]] = mapped_column(String(20)) responsible_mother_last_name: Mapped[Optional[str]] = mapped_column(String(20))
responsible_rfc: Mapped[Optional[str]] = mapped_column(String(30)) responsible_rfc: Mapped[Optional[str]] = mapped_column(String(30))
position: Mapped[Optional[str]] = mapped_column(String(30)) position: Mapped[Optional[str]] = mapped_column(String(30))
# Configuración # Configuración
logo: Mapped[Optional[str]] = mapped_column(String(255)) logo: Mapped[Optional[str]] = mapped_column(String(255))
has_express_line: Mapped[Optional[bool]] = mapped_column(Boolean) has_express_line: Mapped[Optional[bool]] = mapped_column(Boolean)
order_format_type: Mapped[Optional[str]] = mapped_column(String(19)) order_format_type: Mapped[Optional[str]] = mapped_column(String(19))
previous_code: Mapped[Optional[int]] = mapped_column(SmallInteger) previous_code: Mapped[Optional[int]] = mapped_column(SmallInteger)
is_service_company: Mapped[Optional[bool]] = mapped_column(Boolean) is_service_company: Mapped[Optional[bool]] = mapped_column(Boolean)
# Cliente y submaquila # Cliente y submaquila
client_name: Mapped[Optional[str]] = mapped_column(String(300)) client_name: Mapped[Optional[str]] = mapped_column(String(300))
subassembly_mode: Mapped[Optional[str]] = mapped_column(String(7)) subassembly_mode: Mapped[Optional[str]] = mapped_column(String(7))
# Información adicional # Información adicional
curp: Mapped[Optional[str]] = mapped_column(String(19)) curp: Mapped[Optional[str]] = mapped_column(String(19))
inter_db_name: Mapped[Optional[str]] = mapped_column(String(100)) inter_db_name: Mapped[Optional[str]] = mapped_column(String(100))
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()
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) )
updated_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now(), onupdate=func.now()
)
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)

View File

@@ -1,27 +1,30 @@
""" """
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
Only one company can exist per system due to the unique consecutive field. Only one company can exist per system due to the unique consecutive field.
""" """
service = CompanyService(db) service = CompanyService(db)
@@ -30,12 +33,11 @@ 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
Returns the unique company in the system or None if it doesn't exist. Returns the unique company in the system or None if it doesn't exist.
""" """
service = CompanyService(db) service = CompanyService(db)
@@ -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
"""
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. 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)
if not service.delete_company(company_id): companies = service.get_companies_by_tenant(tenant_id)
raise HTTPException(status_code=404, detail=f"Company with ID '{company_id}' not found")
return companies
@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)
@@ -120,19 +97,18 @@ async def get_company_basic_info(
company = service.get_company() company = service.get_company()
if not company: if not company:
raise HTTPException(status_code=404, detail="No company found") raise HTTPException(status_code=404, detail="No company found")
return { return {
"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
@@ -141,21 +117,20 @@ async def get_company_responsible_info(
company = service.get_company() company = service.get_company()
if not company: if not company:
raise HTTPException(status_code=404, detail="No company found") raise HTTPException(status_code=404, detail="No company found")
return { return {
"responsible": company.responsible, "responsible": company.responsible,
"responsible_name": company.responsible_name, "responsible_name": company.responsible_name,
"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
@@ -164,13 +139,67 @@ async def get_company_program_info(
company = service.get_company() company = service.get_company()
if not company: if not company:
raise HTTPException(status_code=404, detail="No company found") raise HTTPException(status_code=404, detail="No company found")
return { return {
"program": company.program, "program": company.program,
"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
@@ -15,29 +16,34 @@ logger = logging.getLogger(__name__)
class CompanyService: class CompanyService:
"""Servicio para gestión de empresa""" """Servicio para gestión de empresa"""
def __init__(self, db: Session): def __init__(self, db: Session):
self.db = db self.db = db
def create_company(self, company_data: CompanyCreateDTO) -> CompanyResponseDTO: def create_company(self, company_data: CompanyCreateDTO) -> CompanyResponseDTO:
""" """
Crea una nueva empresa en el sistema Crea una nueva empresa en el sistema
Args: Args:
company_data: Datos de la empresa a crear company_data: Datos de la empresa a crear
Returns: Returns:
CompanyResponseDTO con información de la empresa creada CompanyResponseDTO con información de la empresa creada
Raises: Raises:
HTTPException: Si ya existe una empresa o error en la creación HTTPException: Si ya existe una empresa o error en la creación
""" """
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(
id=company_data.id, id=company_data.id,
@@ -69,32 +75,35 @@ 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)
self.db.commit() self.db.commit()
self.db.refresh(db_company) self.db.refresh(db_company)
logger.info(f"Company created: {db_company.id} - {db_company.name}") logger.info(f"Company created: {db_company.id} - {db_company.name}")
return CompanyResponseDTO.model_validate(db_company) return CompanyResponseDTO.model_validate(db_company)
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:
self.db.rollback() self.db.rollback()
logger.error(f"Error creating company: {str(e)}") logger.error(f"Error creating company: {str(e)}")
raise HTTPException(status_code=500, detail="Error creating company") raise HTTPException(status_code=500, detail="Error creating company")
def get_company(self) -> Optional[CompanyResponseDTO]: def get_company(self) -> Optional[CompanyResponseDTO]:
""" """
Obtiene la empresa (solo puede haber una) Obtiene la empresa (solo puede haber una)
Returns: Returns:
CompanyResponseDTO o None si no existe CompanyResponseDTO o None si no existe
""" """
@@ -102,14 +111,14 @@ class CompanyService:
if not company: if not company:
return None return None
return CompanyResponseDTO.model_validate(company) return CompanyResponseDTO.model_validate(company)
def get_company_by_id(self, company_id: str) -> Optional[CompanyResponseDTO]: def get_company_by_id(self, company_id: str) -> Optional[CompanyResponseDTO]:
""" """
Obtiene una empresa por ID Obtiene una empresa por ID
Args: Args:
company_id: ID de la empresa company_id: ID de la empresa
Returns: Returns:
CompanyResponseDTO o None si no existe CompanyResponseDTO o None si no existe
""" """
@@ -117,27 +126,29 @@ class CompanyService:
if not company: if not company:
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
Args: Args:
company_id: ID de la empresa a actualizar company_id: ID de la empresa a actualizar
company_data: Datos a actualizar company_data: Datos a actualizar
Returns: Returns:
CompanyResponseDTO actualizada o None si no existe CompanyResponseDTO actualizada o None si no existe
""" """
company = self.db.query(Company).filter(Company.id == company_id).first() company = self.db.query(Company).filter(Company.id == company_id).first()
if not company: if not company:
return None return None
# Actualizar solo campos proporcionados # Actualizar solo campos proporcionados
update_data = company_data.model_dump(exclude_unset=True) update_data = company_data.model_dump(exclude_unset=True)
for field, value in update_data.items(): for field, value in update_data.items():
setattr(company, field, value) setattr(company, field, value)
try: try:
self.db.commit() self.db.commit()
self.db.refresh(company) self.db.refresh(company)
@@ -147,21 +158,21 @@ class CompanyService:
self.db.rollback() self.db.rollback()
logger.error(f"Error updating company {company_id}: {str(e)}") logger.error(f"Error updating company {company_id}: {str(e)}")
raise HTTPException(status_code=500, detail="Error updating company") raise HTTPException(status_code=500, detail="Error updating company")
def delete_company(self, company_id: str) -> bool: def delete_company(self, company_id: str) -> bool:
""" """
Elimina una empresa Elimina una empresa
Args: Args:
company_id: ID de la empresa a eliminar company_id: ID de la empresa a eliminar
Returns: Returns:
True si se eliminó, False si no existe True si se eliminó, False si no existe
""" """
company = self.db.query(Company).filter(Company.id == company_id).first() company = self.db.query(Company).filter(Company.id == company_id).first()
if not company: if not company:
return False return False
try: try:
self.db.delete(company) self.db.delete(company)
self.db.commit() self.db.commit()
@@ -171,14 +182,34 @@ class CompanyService:
self.db.rollback() self.db.rollback()
logger.error(f"Error deleting company {company_id}: {str(e)}") logger.error(f"Error deleting company {company_id}: {str(e)}")
raise HTTPException(status_code=500, detail="Error deleting company") raise HTTPException(status_code=500, detail="Error deleting company")
def exists_company(self) -> bool: def exists_company(self) -> bool:
""" """
Verifica si existe una empresa registrada Verifica si existe una empresa registrada
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,25 +13,42 @@ 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", ForeignKeyConstraint(
name='fk_country_rule_oct_frac_octava' ["company_id"], ["a76.company.id"], name="fk_country_rule_oct_company"
), ),
UniqueConstraint('tenant_id', 'company_id', 'permission', 'line', 'fraction', 'country_code', name='uq_country_rule_oct_permission_line_fraction_country'), ForeignKeyConstraint(
{"schema": "a76"} ["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)
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)
permission: Mapped[str] = mapped_column(String(20)) permission: Mapped[str] = mapped_column(String(20))
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,9 +31,13 @@ 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()
return country return country

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,18 +17,24 @@ 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)
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)
date: Mapped[int] = mapped_column(DateTime) date: Mapped[int] = mapped_column(DateTime)
value: Mapped[Optional[Decimal]] = mapped_column(DECIMAL(13, 6)) value: Mapped[Optional[Decimal]] = mapped_column(DECIMAL(13, 6))
local_currency: Mapped[Optional[str]] = mapped_column(String(7)) local_currency: Mapped[Optional[str]] = mapped_column(String(7))
foreign_currency: Mapped[Optional[str]] = mapped_column(String(7)) foreign_currency: Mapped[Optional[str]] = mapped_column(String(7))

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.
@@ -87,4 +88,4 @@ async def delete_exchange_rate(
exchange_rate = ExchangeRateService.delete_exchange_rate(db, date) exchange_rate = ExchangeRateService.delete_exchange_rate(db, date)
if not exchange_rate: if not exchange_rate:
raise HTTPException(status_code=404, detail="ExchangeRate not found") raise HTTPException(status_code=404, detail="ExchangeRate not found")

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()
@@ -20,4 +27,4 @@ class ExchangeRateService:
if exchange_rate: if exchange_rate:
db.delete(exchange_rate) db.delete(exchange_rate)
db.commit() db.commit()
return exchange_rate return exchange_rate

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,18 +13,28 @@ 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)
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)
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.
@@ -91,4 +98,4 @@ async def delete_fraction(
frac = FractionRuleOctaveService.delete_fraction(db, permission, line, fraction) frac = FractionRuleOctaveService.delete_fraction(db, permission, line, fraction)
if not frac: if not frac:
raise HTTPException(status_code=404, detail="FractionRuleOctave not found") raise HTTPException(status_code=404, detail="FractionRuleOctave not found")

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,8 +31,10 @@ 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()
return frac return frac

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,20 +29,25 @@ 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)
feature_integrations: bool = Field(default=False) feature_integrations: bool = Field(default=False)
feature_dedicated_support: bool = Field(default=False) feature_dedicated_support: bool = Field(default=False)
starts_at: datetime = Field(..., description="Fecha de inicio de vigencia") starts_at: datetime = Field(..., description="Fecha de inicio de vigencia")
expires_at: datetime = Field(..., description="Fecha de expiración") expires_at: datetime = Field(..., description="Fecha de expiración")
class Config: class Config:
json_schema_extra = { json_schema_extra = {
"example": { "example": {
@@ -53,60 +61,63 @@ 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)
max_storage_gb: Optional[int] = Field(None, ge=1) max_storage_gb: Optional[int] = Field(None, ge=1)
max_monthly_operations: Optional[int] = Field(None, ge=1) max_monthly_operations: Optional[int] = Field(None, ge=1)
feature_api_access: Optional[bool] = None feature_api_access: Optional[bool] = None
feature_advanced_reports: Optional[bool] = None feature_advanced_reports: Optional[bool] = None
feature_integrations: Optional[bool] = None feature_integrations: Optional[bool] = None
feature_dedicated_support: Optional[bool] = None feature_dedicated_support: Optional[bool] = None
expires_at: Optional[datetime] = None expires_at: Optional[datetime] = None
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
status: LicenseStatusDTO status: LicenseStatusDTO
max_users: int max_users: int
max_storage_gb: int max_storage_gb: int
max_monthly_operations: int max_monthly_operations: int
feature_api_access: bool feature_api_access: bool
feature_advanced_reports: bool feature_advanced_reports: bool
feature_integrations: bool feature_integrations: bool
feature_dedicated_support: bool feature_dedicated_support: bool
starts_at: datetime starts_at: datetime
expires_at: datetime expires_at: datetime
created_at: datetime created_at: datetime
updated_at: datetime updated_at: datetime
class Config: class Config:
from_attributes = True from_attributes = True
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
expires_at: datetime expires_at: datetime
reason: Optional[str] = None reason: Optional[str] = None
class Config: class Config:
json_schema_extra = { json_schema_extra = {
"example": { "example": {
@@ -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
@@ -128,16 +140,16 @@ class LicenseUsageResponseDTO(BaseModel):
storage_used_gb: int storage_used_gb: int
operations_count: int operations_count: int
api_calls_count: int api_calls_count: int
# Límites actuales # Límites actuales
max_users: int max_users: int
max_storage_gb: int max_storage_gb: int
max_monthly_operations: int max_monthly_operations: int
# Porcentajes de uso # Porcentajes de uso
users_usage_percent: float users_usage_percent: float
storage_usage_percent: float storage_usage_percent: float
operations_usage_percent: float operations_usage_percent: float
class Config: class Config:
from_attributes = True from_attributes = True

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,36 +42,45 @@ 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)
max_storage_gb = Column(Integer, default=10, nullable=False) max_storage_gb = Column(Integer, default=10, nullable=False)
max_monthly_operations = Column(Integer, default=1000, nullable=False) max_monthly_operations = Column(Integer, default=1000, nullable=False)
# Features habilitadas (booleans) # Features habilitadas (booleans)
feature_api_access = Column(Boolean, default=True) feature_api_access = Column(Boolean, default=True)
feature_advanced_reports = Column(Boolean, default=False) feature_advanced_reports = Column(Boolean, default=False)
feature_integrations = Column(Boolean, default=False) feature_integrations = Column(Boolean, default=False)
feature_dedicated_support = Column(Boolean, default=False) feature_dedicated_support = Column(Boolean, default=False)
# Vigencia # Vigencia
starts_at = Column(DateTime(timezone=True), nullable=False) starts_at = Column(DateTime(timezone=True), nullable=False)
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):
return f"<License(tenant_id={self.tenant_id}, plan={self.plan.value}, status={self.status.value})>" return f"<License(tenant_id={self.tenant_id}, plan={self.plan.value}, status={self.status.value})>"
@@ -69,25 +89,32 @@ 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)
period_end = Column(DateTime(timezone=True), nullable=False) period_end = Column(DateTime(timezone=True), nullable=False)
active_users = Column(Integer, default=0) active_users = Column(Integer, default=0)
storage_used_gb = Column(Integer, default=0) storage_used_gb = Column(Integer, default=0)
operations_count = Column(Integer, default=0) operations_count = Column(Integer, default=0)
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):
return f"<LicenseUsage(tenant_id={self.tenant_id}, operations={self.operations_count})>" return f"<LicenseUsage(tenant_id={self.tenant_id}, operations={self.operations_count})>"

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,11 +23,11 @@ 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
Requiere rol: admin Requiere rol: admin
""" """
service = LicenseService(db) service = LicenseService(db)
@@ -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,11 +55,11 @@ 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
Requiere rol: admin Requiere rol: admin
""" """
service = LicenseService(db) service = LicenseService(db)
@@ -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
@@ -110,7 +111,7 @@ async def get_my_license(
tenant_id = getattr(request.state, "tenant_id", None) tenant_id = getattr(request.state, "tenant_id", None)
if not tenant_id: if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in request") raise HTTPException(status_code=400, detail="Tenant ID not found in request")
service = LicenseService(db) service = LicenseService(db)
license = service.get_license_by_tenant(tenant_id) license = service.get_license_by_tenant(tenant_id)
if not license: if not license:

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__)
@@ -22,35 +23,37 @@ logger = logging.getLogger(__name__)
class LicenseService: class LicenseService:
"""Servicio para gestión de licencias""" """Servicio para gestión de licencias"""
def __init__(self, db: Session): def __init__(self, db: Session):
self.db = db self.db = db
def create_license(self, license_data: LicenseCreateDTO) -> LicenseResponseDTO: def create_license(self, license_data: LicenseCreateDTO) -> LicenseResponseDTO:
""" """
Crea una nueva licencia para un tenant Crea una nueva licencia para un tenant
Args: Args:
license_data: Datos de la licencia license_data: Datos de la licencia
Returns: Returns:
LicenseResponseDTO LicenseResponseDTO
Raises: Raises:
HTTPException: Si el tenant ya tiene licencia o hay error HTTPException: Si el tenant ya tiene licencia o hay error
""" """
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
db_license = License( db_license = License(
tenant_id=license_data.tenant_id, tenant_id=license_data.tenant_id,
@@ -64,17 +67,17 @@ 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)
self.db.commit() self.db.commit()
self.db.refresh(db_license) self.db.refresh(db_license)
logger.info(f"License created for tenant {license_data.tenant_id}") logger.info(f"License created for tenant {license_data.tenant_id}")
return LicenseResponseDTO.model_validate(db_license) return LicenseResponseDTO.model_validate(db_license)
except IntegrityError as e: except IntegrityError as e:
self.db.rollback() self.db.rollback()
logger.error(f"IntegrityError creating license: {str(e)}") logger.error(f"IntegrityError creating license: {str(e)}")
@@ -85,14 +88,14 @@ class LicenseService:
self.db.rollback() self.db.rollback()
logger.error(f"Error creating license: {str(e)}") logger.error(f"Error creating license: {str(e)}")
raise HTTPException(status_code=500, detail="Error creating license") raise HTTPException(status_code=500, detail="Error creating license")
def get_license_by_tenant(self, tenant_id: int) -> Optional[LicenseResponseDTO]: def get_license_by_tenant(self, tenant_id: int) -> Optional[LicenseResponseDTO]:
""" """
Obtiene la licencia de un tenant Obtiene la licencia de un tenant
Args: Args:
tenant_id: ID del tenant tenant_id: ID del tenant
Returns: Returns:
LicenseResponseDTO o None si no existe LicenseResponseDTO o None si no existe
""" """
@@ -100,22 +103,24 @@ class LicenseService:
if not license: if not license:
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
Args: Args:
tenant_id: ID del tenant tenant_id: ID del tenant
license_data: Datos a actualizar license_data: Datos a actualizar
Returns: Returns:
LicenseResponseDTO actualizado o None si no existe LicenseResponseDTO actualizado o None si no existe
""" """
license = self.db.query(License).filter(License.tenant_id == tenant_id).first() license = self.db.query(License).filter(License.tenant_id == tenant_id).first()
if not license: if not license:
return None return None
# Actualizar campos proporcionados # Actualizar campos proporcionados
update_data = license_data.model_dump(exclude_unset=True) update_data = license_data.model_dump(exclude_unset=True)
for field, value in update_data.items(): for field, value in update_data.items():
@@ -123,7 +128,7 @@ class LicenseService:
# Convertir enums # Convertir enums
value = LicensePlan(value) if field == "plan" else LicenseStatus(value) value = LicensePlan(value) if field == "plan" else LicenseStatus(value)
setattr(license, field, value) setattr(license, field, value)
try: try:
self.db.commit() self.db.commit()
self.db.refresh(license) self.db.refresh(license)
@@ -133,30 +138,30 @@ class LicenseService:
self.db.rollback() self.db.rollback()
logger.error(f"Error updating license for tenant {tenant_id}: {str(e)}") logger.error(f"Error updating license for tenant {tenant_id}: {str(e)}")
raise HTTPException(status_code=500, detail="Error updating license") raise HTTPException(status_code=500, detail="Error updating license")
def validate_license(self, tenant_id: int) -> dict: def validate_license(self, tenant_id: int) -> dict:
""" """
Valida si la licencia de un tenant está activa y vigente Valida si la licencia de un tenant está activa y vigente
Args: Args:
tenant_id: ID del tenant tenant_id: ID del tenant
Returns: Returns:
Dict con información de validación Dict con información de validación
""" """
license = self.db.query(License).filter(License.tenant_id == tenant_id).first() license = self.db.query(License).filter(License.tenant_id == tenant_id).first()
if not license: if not license:
return { return {
"is_valid": False, "is_valid": False,
"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)
# Verificar estado # Verificar estado
if license.status != LicenseStatus.ACTIVE: if license.status != LicenseStatus.ACTIVE:
return { return {
@@ -164,51 +169,54 @@ 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
if license.expires_at < now: if license.expires_at < now:
# Auto-actualizar a expirada # Auto-actualizar a expirada
license.status = LicenseStatus.EXPIRED license.status = LicenseStatus.EXPIRED
self.db.commit() self.db.commit()
return { return {
"is_valid": False, "is_valid": False,
"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
return { return {
"is_valid": True, "is_valid": True,
"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]:
""" """
Obtiene el uso actual de la licencia de un tenant Obtiene el uso actual de la licencia de un tenant
Args: Args:
tenant_id: ID del tenant tenant_id: ID del tenant
Returns: Returns:
LicenseUsageResponseDTO o None LicenseUsageResponseDTO o None
""" """
license = self.db.query(License).filter(License.tenant_id == tenant_id).first() license = self.db.query(License).filter(License.tenant_id == tenant_id).first()
if not license: if not license:
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
usage = LicenseUsage( usage = LicenseUsage(
@@ -218,14 +226,26 @@ 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,
period_start=usage.period_start, period_start=usage.period_start,
@@ -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,9 +30,10 @@ 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]
class Config: class Config:
from_attributes = True from_attributes = True

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,17 +19,21 @@ 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)
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)
key: Mapped[str] = mapped_column(String(5)) key: Mapped[str] = mapped_column(String(5))
description_es: Mapped[Optional[str]] = mapped_column(String(40)) description_es: Mapped[Optional[str]] = mapped_column(String(40))
description_en: Mapped[Optional[str]] = mapped_column(String(40)) description_en: Mapped[Optional[str]] = mapped_column(String(40))
@@ -29,10 +42,12 @@ class Package(Base):
plural_in: Mapped[Optional[str]] = mapped_column(String(4)) plural_in: Mapped[Optional[str]] = mapped_column(String(4))
code_ace: Mapped[Optional[str]] = mapped_column(String(4)) code_ace: Mapped[Optional[str]] = mapped_column(String(4))
code_aamex: Mapped[Optional[str]] = mapped_column(String(9)) code_aamex: Mapped[Optional[str]] = mapped_column(String(9))
# Timestamps
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
# Timestamps
created_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now()
)
updated_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, default=func.now(), onupdate=func.now()
)
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.
@@ -113,4 +113,4 @@ async def delete_bulto(
bulto = GBultoService.delete_bulto(db, code) bulto = GBultoService.delete_bulto(db, code)
if not bulto: if not bulto:
raise HTTPException(status_code=404, detail="Package not found") raise HTTPException(status_code=404, detail="Package not found")

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.
@@ -34,4 +35,4 @@ class GBultoService:
if bulto: if bulto:
db.delete(bulto) db.delete(bulto)
db.commit() db.commit()
return bulto return bulto

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,43 +11,66 @@ 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
unit_weight: Optional[Decimal] = Field(None, description="Unit weight") unit_weight: Optional[Decimal] = Field(None, description="Unit weight")
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,40 +78,63 @@ 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
unit_weight: Optional[Decimal] = Field(None, description="Unit weight") unit_weight: Optional[Decimal] = Field(None, description="Unit weight")
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
@@ -104,16 +152,16 @@ class PartResponseDTO(BaseModel):
unit_of_measure: Optional[str] = None unit_of_measure: Optional[str] = None
commercial_part_number: Optional[str] = None commercial_part_number: Optional[str] = None
country_of_origin: Optional[str] = None country_of_origin: Optional[str] = None
# Pricing and currency # Pricing and currency
unit_cost: Optional[Decimal] = None unit_cost: Optional[Decimal] = None
currency_type: Optional[str] = None currency_type: Optional[str] = None
currency_key: Optional[str] = None currency_key: Optional[str] = None
# Weight information # Weight information
unit_weight: Optional[Decimal] = None unit_weight: Optional[Decimal] = None
weight_type: Optional[str] = None weight_type: Optional[str] = None
# Classification and regulatory # Classification and regulatory
us_fraction: Optional[str] = None us_fraction: Optional[str] = None
fda_key: Optional[str] = None fda_key: Optional[str] = None
@@ -122,18 +170,18 @@ class PartResponseDTO(BaseModel):
eccn: Optional[str] = None eccn: Optional[str] = None
export_code: Optional[str] = None export_code: Optional[str] = None
exclusion_symbol: Optional[str] = None exclusion_symbol: Optional[str] = None
# Additional information # Additional information
supplier: Optional[str] = None supplier: Optional[str] = None
alternate_unit_measure: Optional[str] = None alternate_unit_measure: Optional[str] = None
added_value: Optional[Decimal] = None added_value: Optional[Decimal] = None
# Status and dates # Status and dates
enabled_disabled: Optional[int] = None enabled_disabled: Optional[int] = None
creation_date: Optional[int] = None creation_date: Optional[int] = None
modification_date: Optional[int] = None modification_date: Optional[int] = None
modification_date_iso: Optional[datetime] = None modification_date_iso: Optional[datetime] = None
# Media # Media
part_photo: Optional[str] = None part_photo: 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,25 +29,34 @@ 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)
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)
# Unique constraint compuesta # Unique constraint compuesta
client_id: Mapped[int] = mapped_column(Integer) client_id: Mapped[int] = mapped_column(Integer)
part_number: Mapped[str] = mapped_column(String(49)) part_number: Mapped[str] = mapped_column(String(49))
# Basic information # Basic information
fraction: Mapped[Optional[str]] = mapped_column(String(10)) fraction: Mapped[Optional[str]] = mapped_column(String(10))
description_spanish: Mapped[Optional[str]] = mapped_column(String(500)) description_spanish: Mapped[Optional[str]] = mapped_column(String(500))
@@ -46,53 +65,59 @@ class Part(Base):
unit_of_measure: Mapped[Optional[str]] = mapped_column(String(5)) unit_of_measure: Mapped[Optional[str]] = mapped_column(String(5))
commercial_part_number: Mapped[Optional[str]] = mapped_column(String(70)) commercial_part_number: Mapped[Optional[str]] = mapped_column(String(70))
country_of_origin: Mapped[Optional[str]] = mapped_column(String(3)) country_of_origin: Mapped[Optional[str]] = mapped_column(String(3))
# Pricing and currency # Pricing and currency
unit_cost: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) unit_cost: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8))
currency_type: Mapped[Optional[str]] = mapped_column(String(2)) currency_type: Mapped[Optional[str]] = mapped_column(String(2))
currency_key: Mapped[Optional[str]] = mapped_column(String(3)) currency_key: Mapped[Optional[str]] = mapped_column(String(3))
# Weight information # Weight information
unit_weight: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 8)) unit_weight: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 8))
weight_type: Mapped[Optional[str]] = mapped_column(String(6)) weight_type: Mapped[Optional[str]] = mapped_column(String(6))
# Classification and regulatory # Classification and regulatory
us_fraction: Mapped[Optional[str]] = mapped_column(String(16)) # FRACCIONAME us_fraction: Mapped[Optional[str]] = mapped_column(String(16)) # FRACCIONAME
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
# Additional information # Additional information
supplier: Mapped[Optional[str]] = mapped_column(String(14)) supplier: Mapped[Optional[str]] = mapped_column(String(14))
alternate_unit_measure: Mapped[Optional[str]] = mapped_column(String(14)) alternate_unit_measure: Mapped[Optional[str]] = mapped_column(String(14))
added_value: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) added_value: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8))
# Status and dates # Status and dates
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
part_class_info: Mapped[Optional["Class"]] = relationship( part_class_info: Mapped[Optional["Class"]] = relationship(
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
@@ -9,12 +10,12 @@ 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
from .service import PartService from .service import PartService
from .dto import ( from .dto import (
PartCreateDTO, PartCreateDTO,
PartUpdateDTO, PartUpdateDTO,
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)
@@ -197,8 +199,8 @@ async def get_part(
part = service.get_part(client_id, part_number) part = service.get_part(client_id, part_number)
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
@@ -225,8 +227,8 @@ async def update_part(
part = service.update_part(client_id, part_number, part_data) part = service.update_part(client_id, part_number, part_data)
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,11 +238,11 @@ 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
Note: This will completely remove the part from the system. Note: This will completely remove the part from the system.
""" """
# Validate access to the tenant and company # Validate access to the tenant and company
@@ -253,17 +255,19 @@ async def delete_part(
service = PartService(db) service = PartService(db)
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
@@ -279,8 +283,8 @@ async def toggle_part_status(
part = service.toggle_status(client_id, part_number) part = service.toggle_status(client_id, part_number)
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
@@ -307,10 +311,10 @@ async def get_part_basic_info(
part = service.get_part(client_id, part_number) part = service.get_part(client_id, part_number)
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(
client_id=part.client_id, client_id=part.client_id,
part_number=part.part_number, part_number=part.part_number,
@@ -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.)
@@ -344,10 +348,10 @@ async def get_part_regulatory_info(
part = service.get_part(client_id, part_number) part = service.get_part(client_id, part_number)
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 {
"client_id": part.client_id, "client_id": part.client_id,
"part_number": part.part_number, "part_number": part.part_number,
@@ -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,55 +50,58 @@ 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")
@staticmethod @staticmethod
def get_parts_paginated( def get_parts_paginated(
db: Session, db: Session,
skip: int = 0, skip: int = 0,
limit: int = 100, limit: int = 100,
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
""" """
try: try:
query = db.query(Part) query = db.query(Part)
# 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)
if fraction: if fraction:
query = query.filter(Part.fraction == fraction) query = query.filter(Part.fraction == fraction)
if country_of_origin: if country_of_origin:
query = query.filter(Part.country_of_origin == country_of_origin) query = query.filter(Part.country_of_origin == country_of_origin)
# Contar total # Contar total
total = query.count() total = query.count()
# Aplicar paginación # Aplicar paginación
parts = query.offset(skip).limit(limit).all() parts = query.offset(skip).limit(limit).all()
return parts, total return parts, total
except Exception as e: except Exception as e:
logger.error(f"Error getting paginated parts: {e}") logger.error(f"Error getting paginated parts: {e}")
@@ -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
""" """
@@ -158,11 +177,11 @@ class PartService:
db_part = PartService.get_part(db, client_id, part_number) db_part = PartService.get_part(db, client_id, part_number)
if not db_part: if not db_part:
return None return None
# Actualizar campos # Actualizar campos
for field, value in part_data.model_dump(exclude_unset=True).items(): for field, value in part_data.model_dump(exclude_unset=True).items():
setattr(db_part, field, value) setattr(db_part, field, value)
db.commit() db.commit()
db.refresh(db_part) db.refresh(db_part)
return db_part return db_part
@@ -180,7 +199,7 @@ class PartService:
db_part = PartService.get_part(db, client_id, part_number) db_part = PartService.get_part(db, client_id, part_number)
if not db_part: if not db_part:
return False return False
db.delete(db_part) db.delete(db_part)
db.commit() db.commit()
return True return True
@@ -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
""" """
@@ -198,10 +219,10 @@ class PartService:
db_part = PartService.get_part(db, client_id, part_number) db_part = PartService.get_part(db, client_id, part_number)
if not db_part: if not db_part:
return None return None
# Toggle status (assuming 1 = enabled, 0 = disabled) # Toggle status (assuming 1 = enabled, 0 = disabled)
db_part.enabled_disabled = 1 if db_part.enabled_disabled == 0 else 0 db_part.enabled_disabled = 1 if db_part.enabled_disabled == 0 else 0
db.commit() db.commit()
db.refresh(db_part) db.refresh(db_part)
return db_part return db_part
@@ -217,37 +238,49 @@ class PartService:
""" """
try: try:
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()
disabled_parts = db.query(Part).filter(Part.enabled_disabled == 0).count() disabled_parts = db.query(Part).filter(Part.enabled_disabled == 0).count()
return { return {
"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
""" """
@@ -255,7 +288,7 @@ class PartService:
db_part = PartService.get_part(db, client_id, part_number) db_part = PartService.get_part(db, client_id, part_number)
if not db_part: if not db_part:
return None return None
return { return {
"client_id": db_part.client_id, "client_id": db_part.client_id,
"part_number": db_part.part_number, "part_number": db_part.part_number,
@@ -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,22 +17,33 @@ 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)
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)
dta_type: Mapped[str] = mapped_column(String(1)) dta_type: Mapped[str] = mapped_column(String(1))
dta_operation: Mapped[int] = mapped_column(SmallInteger) dta_operation: Mapped[int] = mapped_column(SmallInteger)
dta_vehicle_count: Mapped[int] = mapped_column(SmallInteger) dta_vehicle_count: Mapped[int] = mapped_column(SmallInteger)
@@ -33,10 +54,16 @@ class PedimentoConfigCalculations(Base):
fixed_vehicle_dta_fee: Mapped[int] = mapped_column(SmallInteger) fixed_vehicle_dta_fee: Mapped[int] = mapped_column(SmallInteger)
additional_fixed_fee: Mapped[int] = mapped_column(SmallInteger) additional_fixed_fee: Mapped[int] = mapped_column(SmallInteger)
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,21 +21,39 @@ 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)
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)
is_embassy: Mapped[int] = mapped_column(SmallInteger) is_embassy: Mapped[int] = mapped_column(SmallInteger)
embassy_dta: Mapped[Decimal] = mapped_column(Numeric(11, 2)) embassy_dta: Mapped[Decimal] = mapped_column(Numeric(11, 2))
rule_3121_section_ii: Mapped[int] = mapped_column(SmallInteger) rule_3121_section_ii: Mapped[int] = mapped_column(SmallInteger)
@@ -37,10 +65,16 @@ class PedimentoConfigParameters(Base):
customs_value_per_item: Mapped[int] = mapped_column(SmallInteger) customs_value_per_item: Mapped[int] = mapped_column(SmallInteger)
is_national_supplier: Mapped[int] = mapped_column(SmallInteger) is_national_supplier: Mapped[int] = mapped_column(SmallInteger)
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,32 +17,57 @@ 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)
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)
surcharge_igi: Mapped[int] = mapped_column(SmallInteger) surcharge_igi: Mapped[int] = mapped_column(SmallInteger)
surcharge_dta: Mapped[int] = mapped_column(SmallInteger) surcharge_dta: Mapped[int] = mapped_column(SmallInteger)
surcharge_vat: Mapped[int] = mapped_column(SmallInteger) surcharge_vat: Mapped[int] = mapped_column(SmallInteger)
surcharge_isan: Mapped[int] = mapped_column(SmallInteger) surcharge_isan: Mapped[int] = mapped_column(SmallInteger)
surcharge_ieps: Mapped[int] = mapped_column(SmallInteger) surcharge_ieps: Mapped[int] = mapped_column(SmallInteger)
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,31 +17,56 @@ 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)
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)
update_vat: Mapped[int] = mapped_column(SmallInteger) update_vat: Mapped[int] = mapped_column(SmallInteger)
update_advalorem: Mapped[int] = mapped_column(SmallInteger) update_advalorem: Mapped[int] = mapped_column(SmallInteger)
update_cc: Mapped[int] = mapped_column(SmallInteger) update_cc: Mapped[int] = mapped_column(SmallInteger)
update_ieps: Mapped[int] = mapped_column(SmallInteger) update_ieps: Mapped[int] = mapped_column(SmallInteger)
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,30 +17,53 @@ 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)
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)
update_vat: Mapped[int] = mapped_column(SmallInteger) update_vat: Mapped[int] = mapped_column(SmallInteger)
update_advalorem: Mapped[int] = mapped_column(SmallInteger) update_advalorem: Mapped[int] = mapped_column(SmallInteger)
update_cc: Mapped[int] = mapped_column(SmallInteger) update_cc: Mapped[int] = mapped_column(SmallInteger)
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,28 +17,53 @@ 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)
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)
dispatch_customs: Mapped[str] = mapped_column(String(3)) dispatch_customs: Mapped[str] = mapped_column(String(3))
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,23 +18,38 @@ 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)
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)
entry_date: Mapped[datetime] = mapped_column(DateTime) entry_date: Mapped[datetime] = mapped_column(DateTime)
pedimento_date: Mapped[datetime] = mapped_column(DateTime) pedimento_date: Mapped[datetime] = mapped_column(DateTime)
payment_date: Mapped[datetime] = mapped_column(DateTime) payment_date: Mapped[datetime] = mapped_column(DateTime)
@@ -37,10 +62,16 @@ class PedimentoDates(Base):
end_date: Mapped[datetime] = mapped_column(DateTime) end_date: Mapped[datetime] = mapped_column(DateTime)
capture_date: Mapped[datetime] = mapped_column(DateTime) capture_date: Mapped[datetime] = mapped_column(DateTime)
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,22 +20,39 @@ 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)
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)
freight: Mapped[Decimal] = mapped_column(Numeric(13, 2)) freight: Mapped[Decimal] = mapped_column(Numeric(13, 2))
insurance: Mapped[Decimal] = mapped_column(Numeric(13, 2)) insurance: Mapped[Decimal] = mapped_column(Numeric(13, 2))
loading: Mapped[Decimal] = mapped_column(Numeric(13, 2)) loading: Mapped[Decimal] = mapped_column(Numeric(13, 2))
@@ -34,10 +62,16 @@ class PedimentoDecrementables(Base):
currency_factor: Mapped[Decimal] = mapped_column(Numeric(15, 8)) currency_factor: Mapped[Decimal] = mapped_column(Numeric(15, 8))
not_affect_usd_value: Mapped[int] = mapped_column(SmallInteger) not_affect_usd_value: Mapped[int] = mapped_column(SmallInteger)
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,22 +20,39 @@ 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)
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)
insured_value: Mapped[Decimal] = mapped_column(Numeric(13, 2)) insured_value: Mapped[Decimal] = mapped_column(Numeric(13, 2))
freight: Mapped[Decimal] = mapped_column(Numeric(13, 2)) freight: Mapped[Decimal] = mapped_column(Numeric(13, 2))
insurance: Mapped[Decimal] = mapped_column(Numeric(13, 2)) insurance: Mapped[Decimal] = mapped_column(Numeric(13, 2))
@@ -35,10 +63,16 @@ class PedimentoIncrementables(Base):
currency_factor: Mapped[Decimal] = mapped_column(Numeric(15, 8)) currency_factor: Mapped[Decimal] = mapped_column(Numeric(15, 8))
not_affect_usd_value: Mapped[int] = mapped_column(SmallInteger) not_affect_usd_value: Mapped[int] = mapped_column(SmallInteger)
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,29 +19,50 @@ 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)
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)
update_factor_type: Mapped[int] = mapped_column(SmallInteger) update_factor_type: Mapped[int] = mapped_column(SmallInteger)
update_factor: Mapped[Decimal] = mapped_column(Numeric(7, 4)) update_factor: Mapped[Decimal] = mapped_column(Numeric(7, 4))
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,24 +21,39 @@ 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)
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)
payment_id: Mapped[int] = mapped_column(Integer) payment_id: Mapped[int] = mapped_column(Integer)
acknowledgment: Mapped[str] = mapped_column(String(20)) acknowledgment: Mapped[str] = mapped_column(String(20))
operation_number: Mapped[str] = mapped_column(String(14)) operation_number: Mapped[str] = mapped_column(String(14))
bank_code: Mapped[int] = mapped_column(Integer) bank_code: Mapped[int] = mapped_column(Integer)
@@ -36,11 +64,17 @@ class PedimentoPayments(Base):
total_cash_paid: Mapped[int] = mapped_column(Integer) total_cash_paid: Mapped[int] = mapped_column(Integer)
total_contributions: Mapped[int] = mapped_column(Integer) total_contributions: Mapped[int] = mapped_column(Integer)
counter_payment: Mapped[int] = mapped_column(SmallInteger) counter_payment: Mapped[int] = mapped_column(SmallInteger)
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,30 +16,55 @@ 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)
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)
destination_pedimento_year: Mapped[str] = mapped_column(String(2)) destination_pedimento_year: Mapped[str] = mapped_column(String(2))
destination_customs_office: Mapped[str] = mapped_column(String(3)) destination_customs_office: Mapped[str] = mapped_column(String(3))
destination_license: Mapped[str] = mapped_column(String(4)) destination_license: Mapped[str] = mapped_column(String(4))
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,22 +17,41 @@ 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)
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)
original_pedimento_year: Mapped[str] = mapped_column(String(2)) original_pedimento_year: Mapped[str] = mapped_column(String(2))
original_customs_office: Mapped[str] = mapped_column(String(3)) original_customs_office: Mapped[str] = mapped_column(String(3))
original_license: Mapped[str] = mapped_column(String(4)) original_license: Mapped[str] = mapped_column(String(4))
@@ -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,26 +17,49 @@ 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)
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)
destination: Mapped[int] = mapped_column(SmallInteger) destination: Mapped[int] = mapped_column(SmallInteger)
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,35 +17,50 @@ 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)
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)
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,50 +1,110 @@
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
from enum import IntEnum 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)
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)
year: Mapped[str] = mapped_column(String(2)) year: Mapped[str] = mapped_column(String(2))
customs_office: Mapped[str] = mapped_column(String(2)) customs_office: Mapped[str] = mapped_column(String(2))
license: Mapped[str] = mapped_column(String(4)) license: Mapped[str] = mapped_column(String(4))
@@ -59,26 +119,69 @@ class Pedimentos(Base):
paid_price: Mapped[Optional[Decimal]] = mapped_column(Numeric(17, 6)) paid_price: Mapped[Optional[Decimal]] = mapped_column(Numeric(17, 6))
gross_weight: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 3)) gross_weight: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 3))
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,39 +1,119 @@
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
from .routes.pedimento_decrementables import router as pedimento_decrementables_router from .routes.pedimento_decrementables import router as pedimento_decrementables_router
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,14 +24,17 @@ 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")
return config return config
@@ -39,14 +44,15 @@ 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:
raise HTTPException(status_code=400, detail="Pedimento ID mismatch") raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
config = PedimentoConfigAdditionalService.create(db, data, tenant_id, company_id) config = PedimentoConfigAdditionalService.create(db, data, tenant_id, company_id)
return config return config
@@ -57,14 +63,17 @@ 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")
return config return config
@@ -73,12 +82,15 @@ 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")
return None return None

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,14 +24,17 @@ 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")
return config return config
@@ -39,14 +44,15 @@ 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:
raise HTTPException(status_code=400, detail="Pedimento ID mismatch") raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
config = PedimentoConfigCalculationsService.create(db, data, tenant_id, company_id) config = PedimentoConfigCalculationsService.create(db, data, tenant_id, company_id)
return config return config
@@ -57,14 +63,17 @@ 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")
return config return config
@@ -73,12 +82,13 @@ 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:
raise HTTPException(status_code=404, detail="Config calculations not found") raise HTTPException(status_code=404, detail="Config calculations not found")
return None return None

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,14 +24,17 @@ 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")
return config return config
@@ -39,14 +44,15 @@ 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:
raise HTTPException(status_code=400, detail="Pedimento ID mismatch") raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
config = PedimentoConfigParametersService.create(db, data, tenant_id, company_id) config = PedimentoConfigParametersService.create(db, data, tenant_id, company_id)
return config return config
@@ -57,14 +63,17 @@ 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")
return config return config
@@ -73,12 +82,15 @@ 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")
return None return None

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,14 +24,17 @@ 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")
return config return config
@@ -39,14 +44,15 @@ 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:
raise HTTPException(status_code=400, detail="Pedimento ID mismatch") raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
config = PedimentoConfigSurchargesService.create(db, data, tenant_id, company_id) config = PedimentoConfigSurchargesService.create(db, data, tenant_id, company_id)
return config return config
@@ -57,14 +63,17 @@ 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")
return config return config
@@ -73,12 +82,15 @@ 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")
return None return None

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,14 +71,19 @@ 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,14 +24,17 @@ 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")
return config return config
@@ -38,15 +43,16 @@ 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:
raise HTTPException(status_code=400, detail="Pedimento ID mismatch") raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
config = PedimentoConfigUpdatesService.create(db, data, tenant_id, company_id) config = PedimentoConfigUpdatesService.create(db, data, tenant_id, company_id)
return config return config
@@ -57,14 +63,17 @@ 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")
return config return config
@@ -73,12 +82,15 @@ 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")
return None return None

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,14 +40,17 @@ 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")
return office return office
@@ -53,14 +60,15 @@ 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:
raise HTTPException(status_code=400, detail="Pedimento ID mismatch") raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
office = PedimentoCustomsOfficesService.create(db, data, tenant_id, company_id) office = PedimentoCustomsOfficesService.create(db, data, tenant_id, company_id)
return office return office
@@ -72,14 +80,17 @@ 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")
return office return office
@@ -89,12 +100,15 @@ 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")
return None return None

View File

@@ -1,48 +1,55 @@
""" """
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")
return dates return dates
@router.post("/", response_model=PedimentoDatesResponse, status_code=201) @router.post("/", response_model=PedimentoDatesResponse, status_code=201)
async def create_dates( 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,14 +60,15 @@ 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:
raise HTTPException(status_code=404, detail="Pedimento dates not found") raise HTTPException(status_code=404, detail="Pedimento dates not found")
return dates return dates
@@ -69,12 +77,13 @@ 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:
raise HTTPException(status_code=404, detail="Pedimento dates not found") raise HTTPException(status_code=404, detail="Pedimento dates not found")
return None return None

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,14 +41,17 @@ 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")
return decrementable return decrementable
@@ -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,14 +83,17 @@ 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")
return decrementable return decrementable
@@ -90,12 +103,15 @@ 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")
return None return None

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,14 +41,17 @@ 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")
return incrementable return incrementable
@@ -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,14 +83,17 @@ 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")
return incrementable return incrementable
@@ -90,12 +103,15 @@ 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")
return None return None

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,14 +24,17 @@ 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")
return indexes return indexes
@@ -39,14 +44,15 @@ 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:
raise HTTPException(status_code=400, detail="Pedimento ID mismatch") raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
indexes = PedimentoIndexesService.create(db, data, tenant_id, company_id) indexes = PedimentoIndexesService.create(db, data, tenant_id, company_id)
return indexes return indexes
@@ -57,14 +63,17 @@ 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")
return indexes return indexes
@@ -73,12 +82,13 @@ 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:
raise HTTPException(status_code=404, detail="Pedimento indexes not found") raise HTTPException(status_code=404, detail="Pedimento indexes not found")
return None return None

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,14 +41,17 @@ 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")
return payment return payment
@@ -54,14 +61,15 @@ 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:
raise HTTPException(status_code=400, detail="Pedimento ID mismatch") raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
payment = PedimentoPaymentsService.create(db, data, tenant_id, company_id) payment = PedimentoPaymentsService.create(db, data, tenant_id, company_id)
return payment return payment
@@ -73,14 +81,17 @@ 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")
return payment return payment
@@ -90,12 +101,15 @@ 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")
return None return None

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,14 +71,19 @@ 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,14 +26,17 @@ 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")
return rectification return rectification
@@ -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,14 +67,17 @@ 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")
return rectification return rectification
@@ -73,12 +86,15 @@ 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")
return None return None

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,14 +41,17 @@ 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")
return transport_mean return transport_mean
@@ -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,14 +83,17 @@ 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")
return transport_mean return transport_mean
@@ -90,12 +103,15 @@ 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")
return None return None

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