Merge pull request 'catalogos-frontend' (#8) from catalogos-frontend into development

Reviewed-on: ADUANASOFT/anexo76#8
This commit is contained in:
2025-11-07 02:31:16 +00:00
359 changed files with 28883 additions and 463 deletions

4
.gitignore vendored
View File

@@ -15,18 +15,18 @@ downloads/
eggs/
.eggs/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
.pnpm-store/
# Environment
.env
.env.local
backend/SCRIPTS/
# IDEs
.vscode/
.idea/

View File

@@ -84,7 +84,8 @@ path_separator = os
# database URL. This is consumed by the user-maintained env.py script only.
# other means of configuring database URLs may be customized within the env.py
# file.
sqlalchemy.url = ${DATABASE_URL}
sqlalchemy.url = postgresql://${CORE_DB_USER}:${CORE_DB_PASSWORD}@${CORE_DB_HOST}:${CORE_DB_PORT}/${CORE_DB_NAME}
[post_write_hooks]

View File

@@ -78,8 +78,9 @@ fileConfig(config.config_file_name)
target_metadata = Base.metadata
def import_models_from_dir(dir_path: str):
"""Importa recursivamente cualquier archivo models.py desde dir_path"""
"""Importa recursivamente cualquier archivo models.py desde dir_path y archivos en directorios models/"""
for root, dirs, files in os.walk(dir_path):
# Importar archivos models.py directos
if "models.py" in files:
module_path = os.path.join(root, "models.py")
# Convertir path en nombre de módulo compatible
@@ -89,6 +90,20 @@ def import_models_from_dir(dir_path: str):
spec = importlib.util.spec_from_file_location(module_name, module_path)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
# Importar todos los archivos .py en directorios llamados "models"
if os.path.basename(root) == "models":
for file in files:
if file.endswith(".py") and not file.startswith("__"):
module_path = os.path.join(root, file)
rel_path = os.path.relpath(module_path, BASE_DIR)
module_name = rel_path.replace(os.sep, ".").replace(".py", "")
try:
spec = importlib.util.spec_from_file_location(module_name, module_path)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
except Exception as e:
logger.warning(f"No se pudo importar {module_path}: {e}")
# Importar todos los models dentro de api/v1/modules y api/v1/modules/uploads
modules_dir = os.path.join(BASE_DIR, "api", "v1", "modules")

View File

@@ -0,0 +1,424 @@
"""Pedimentos
Revision ID: 03b786378f94
Revises: 7937209f9718
Create Date: 2025-11-06 17:07:16.536298
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '03b786378f94'
down_revision: Union[str, Sequence[str], None] = '54f2046774d0'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('pedimentos',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('tenant_id', sa.Integer(), nullable=False),
sa.Column('year', sa.String(length=2), nullable=True),
sa.Column('customs_office', sa.String(length=2), nullable=True),
sa.Column('license', sa.String(length=4), nullable=True),
sa.Column('pedimento_number', sa.String(length=7), nullable=True),
sa.Column('client_id', sa.Integer(), nullable=True),
sa.Column('operation_type', sa.Integer(), nullable=True),
sa.Column('pedimento_type', sa.Integer(), nullable=True),
sa.Column('pedimento_key', sa.String(length=2), nullable=True),
sa.Column('regime', sa.String(length=3), nullable=True),
sa.Column('status', sa.String(length=30), nullable=True),
sa.Column('usd_value', sa.Numeric(precision=17, scale=6), nullable=True),
sa.Column('paid_price', sa.Numeric(precision=17, scale=6), nullable=True),
sa.Column('gross_weight', sa.Numeric(precision=19, scale=3), nullable=True),
sa.Column('exchange_rate', sa.Numeric(precision=9, scale=5), nullable=True),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True),
sa.ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], ),
sa.PrimaryKeyConstraint('id', name='pedimentos_pkey'),
schema='a76'
)
op.create_index('idx_pedimentos_client_id', 'pedimentos', ['client_id'], unique=False, schema='a76')
op.create_index('idx_pedimentos_created_at', 'pedimentos', ['created_at'], unique=False, schema='a76')
op.create_index('idx_pedimentos_status', 'pedimentos', ['status'], unique=False, schema='a76')
op.create_index(op.f('ix_a76_pedimentos_tenant_id'), 'pedimentos', ['tenant_id'], unique=False, schema='a76')
op.create_table('pedimento_config_additional',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('pedimento_id', sa.Integer(), nullable=False),
sa.Column('tenant_id', sa.Integer(), nullable=False),
sa.Column('add_po_identifier', sa.SmallInteger(), nullable=True),
sa.Column('do_not_exempt_norms_complement_x', sa.SmallInteger(), nullable=True),
sa.Column('manual_pedimento_year', sa.String(length=2), nullable=True),
sa.Column('enable_import_invoice_recipient', sa.SmallInteger(), nullable=True),
sa.Column('send_502_validation_file_for_consolidated', sa.SmallInteger(), nullable=True),
sa.Column('add_remove_norms', sa.SmallInteger(), nullable=True),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True),
sa.ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], name='fk_pedimento_config_additional', ondelete='CASCADE'),
sa.ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], ),
sa.PrimaryKeyConstraint('id', name='pedimento_config_additional_pkey'),
sa.UniqueConstraint('pedimento_id', name='pedimento_config_additional_pedimento_id_key'),
schema='a76'
)
op.create_index(op.f('ix_a76_pedimento_config_additional_tenant_id'), 'pedimento_config_additional', ['tenant_id'], unique=False, schema='a76')
op.create_table('pedimento_config_calculations',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('pedimento_id', sa.Integer(), nullable=False),
sa.Column('tenant_id', sa.Integer(), nullable=False),
sa.Column('dta_type', sa.String(length=1), nullable=True),
sa.Column('dta_operation', sa.SmallInteger(), nullable=True),
sa.Column('dta_vehicle_count', sa.SmallInteger(), nullable=True),
sa.Column('dta_mixed_rate_8permil', sa.SmallInteger(), nullable=True),
sa.Column('pays_vat', sa.SmallInteger(), nullable=True),
sa.Column('pays_prevalidation', sa.SmallInteger(), nullable=True),
sa.Column('include_sagar_certificate_fee', sa.SmallInteger(), nullable=True),
sa.Column('fixed_vehicle_dta_fee', sa.SmallInteger(), nullable=True),
sa.Column('additional_fixed_fee', sa.SmallInteger(), nullable=True),
sa.Column('additional_fixed_fee_payment_method', sa.SmallInteger(), nullable=True),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True),
sa.ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], name='fk_pedimento_config_calculations', ondelete='CASCADE'),
sa.ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], ),
sa.PrimaryKeyConstraint('id', name='pedimento_config_calculations_pkey'),
sa.UniqueConstraint('pedimento_id', name='pedimento_config_calculations_pedimento_id_key'),
schema='a76'
)
op.create_index(op.f('ix_a76_pedimento_config_calculations_tenant_id'), 'pedimento_config_calculations', ['tenant_id'], unique=False, schema='a76')
op.create_table('pedimento_config_parameters',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('pedimento_id', sa.Integer(), nullable=False),
sa.Column('tenant_id', sa.Integer(), nullable=False),
sa.Column('is_embassy', sa.SmallInteger(), nullable=True),
sa.Column('embassy_dta', sa.Numeric(precision=11, scale=2), nullable=True),
sa.Column('rule_3121_section_ii', sa.SmallInteger(), nullable=True),
sa.Column('use_previous_tariff', sa.SmallInteger(), nullable=True),
sa.Column('use_payment_date_fi', sa.SmallInteger(), nullable=True),
sa.Column('add_state_supplier_record_505', sa.SmallInteger(), nullable=True),
sa.Column('customs_value_calculation', sa.SmallInteger(), nullable=True),
sa.Column('two_decimals_unit_value', sa.SmallInteger(), nullable=True),
sa.Column('customs_value_per_item', sa.SmallInteger(), nullable=True),
sa.Column('is_national_supplier', sa.SmallInteger(), nullable=True),
sa.Column('is_consolidated', sa.SmallInteger(), nullable=True),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True),
sa.ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], name='fk_pedimento_config_parameters', ondelete='CASCADE'),
sa.ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], ),
sa.PrimaryKeyConstraint('id', name='pedimento_config_parameters_pkey'),
sa.UniqueConstraint('pedimento_id', name='pedimento_config_parameters_pedimento_id_key'),
schema='a76'
)
op.create_index(op.f('ix_a76_pedimento_config_parameters_tenant_id'), 'pedimento_config_parameters', ['tenant_id'], unique=False, schema='a76')
op.create_table('pedimento_config_surcharges',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('pedimento_id', sa.Integer(), nullable=False),
sa.Column('tenant_id', sa.Integer(), nullable=False),
sa.Column('surcharge_igi', sa.SmallInteger(), nullable=True),
sa.Column('surcharge_dta', sa.SmallInteger(), nullable=True),
sa.Column('surcharge_vat', sa.SmallInteger(), nullable=True),
sa.Column('surcharge_isan', sa.SmallInteger(), nullable=True),
sa.Column('surcharge_ieps', sa.SmallInteger(), nullable=True),
sa.Column('surcharge_cc', sa.SmallInteger(), nullable=True),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True),
sa.ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], name='fk_pedimento_config_surcharges', ondelete='CASCADE'),
sa.ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], ),
sa.PrimaryKeyConstraint('id', name='pedimento_config_surcharges_pkey'),
sa.UniqueConstraint('pedimento_id', name='pedimento_config_surcharges_pedimento_id_key'),
schema='a76'
)
op.create_index(op.f('ix_a76_pedimento_config_surcharges_tenant_id'), 'pedimento_config_surcharges', ['tenant_id'], unique=False, schema='a76')
op.create_table('pedimento_config_update_rectification',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('pedimento_id', sa.Integer(), nullable=False),
sa.Column('tenant_id', sa.Integer(), nullable=False),
sa.Column('update_vat', sa.SmallInteger(), nullable=True),
sa.Column('update_advalorem', sa.SmallInteger(), nullable=True),
sa.Column('update_cc', sa.SmallInteger(), nullable=True),
sa.Column('update_ieps', sa.SmallInteger(), nullable=True),
sa.Column('calculate_surcharge', sa.SmallInteger(), nullable=True),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True),
sa.ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], name='fk_pedimento_config_update_rectification', ondelete='CASCADE'),
sa.ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], ),
sa.PrimaryKeyConstraint('id', name='pedimento_config_update_rectification_pkey'),
sa.UniqueConstraint('pedimento_id', name='pedimento_config_update_rectification_pedimento_id_key'),
schema='a76'
)
op.create_index(op.f('ix_a76_pedimento_config_update_rectification_tenant_id'), 'pedimento_config_update_rectification', ['tenant_id'], unique=False, schema='a76')
op.create_table('pedimento_config_updates',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('pedimento_id', sa.Integer(), nullable=False),
sa.Column('tenant_id', sa.Integer(), nullable=False),
sa.Column('update_vat', sa.SmallInteger(), nullable=True),
sa.Column('update_advalorem', sa.SmallInteger(), nullable=True),
sa.Column('update_cc', sa.SmallInteger(), nullable=True),
sa.Column('update_ieps', sa.SmallInteger(), nullable=True),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True),
sa.ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], name='fk_pedimento_config_updates', ondelete='CASCADE'),
sa.ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], ),
sa.PrimaryKeyConstraint('id', name='pedimento_config_updates_pkey'),
sa.UniqueConstraint('pedimento_id', name='pedimento_config_updates_pedimento_id_key'),
schema='a76'
)
op.create_index(op.f('ix_a76_pedimento_config_updates_tenant_id'), 'pedimento_config_updates', ['tenant_id'], unique=False, schema='a76')
op.create_table('pedimento_customs_offices',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('pedimento_id', sa.Integer(), nullable=False),
sa.Column('tenant_id', sa.Integer(), nullable=False),
sa.Column('dispatch_customs', sa.String(length=3), nullable=True),
sa.Column('entry_exit_customs', sa.String(length=3), nullable=True),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True),
sa.ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], name='fk_pedimento_customs_offices', ondelete='CASCADE'),
sa.ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], ),
sa.PrimaryKeyConstraint('id', name='pedimento_customs_offices_pkey'),
sa.UniqueConstraint('pedimento_id', name='pedimento_customs_offices_pedimento_id_key'),
schema='a76'
)
op.create_index(op.f('ix_a76_pedimento_customs_offices_tenant_id'), 'pedimento_customs_offices', ['tenant_id'], unique=False, schema='a76')
op.create_table('pedimento_dates',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('pedimento_id', sa.Integer(), nullable=False),
sa.Column('tenant_id', sa.Integer(), nullable=False),
sa.Column('entry_date', sa.DateTime(), nullable=True),
sa.Column('pedimento_date', sa.DateTime(), nullable=True),
sa.Column('payment_date', sa.DateTime(), nullable=True),
sa.Column('rectification_payment_date', sa.DateTime(), nullable=True),
sa.Column('extraction_date', sa.DateTime(), nullable=True),
sa.Column('submission_date', sa.DateTime(), nullable=True),
sa.Column('eucan_date', sa.DateTime(), nullable=True),
sa.Column('original_date', sa.DateTime(), nullable=True),
sa.Column('start_date', sa.DateTime(), nullable=True),
sa.Column('end_date', sa.DateTime(), nullable=True),
sa.Column('capture_date', sa.DateTime(), nullable=True),
sa.Column('capture_time', sa.Time(), nullable=True),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True),
sa.ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], name='fk_pedimento_dates', ondelete='CASCADE'),
sa.ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], ),
sa.PrimaryKeyConstraint('id', name='pedimento_dates_pkey'),
sa.UniqueConstraint('pedimento_id', name='pedimento_dates_pedimento_id_key'),
schema='a76'
)
op.create_index('idx_pedimento_dates_pedimento_id', 'pedimento_dates', ['pedimento_id'], unique=False, schema='a76')
op.create_index(op.f('ix_a76_pedimento_dates_tenant_id'), 'pedimento_dates', ['tenant_id'], unique=False, schema='a76')
op.create_table('pedimento_decrementables',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('pedimento_id', sa.Integer(), nullable=False),
sa.Column('tenant_id', sa.Integer(), nullable=False),
sa.Column('freight', sa.Numeric(precision=13, scale=2), nullable=True),
sa.Column('insurance', sa.Numeric(precision=13, scale=2), nullable=True),
sa.Column('loading', sa.Numeric(precision=13, scale=2), nullable=True),
sa.Column('unloading', sa.Numeric(precision=13, scale=2), nullable=True),
sa.Column('others', sa.Numeric(precision=13, scale=2), nullable=True),
sa.Column('currency', sa.String(length=3), nullable=True),
sa.Column('currency_factor', sa.Numeric(precision=15, scale=8), nullable=True),
sa.Column('not_affect_usd_value', sa.SmallInteger(), nullable=True),
sa.Column('not_affect_customs_value', sa.SmallInteger(), nullable=True),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True),
sa.ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], name='fk_pedimento_decrementables', ondelete='CASCADE'),
sa.ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], ),
sa.PrimaryKeyConstraint('id', name='pedimento_decrementables_pkey'),
sa.UniqueConstraint('pedimento_id', name='pedimento_decrementables_pedimento_id_key'),
schema='a76'
)
op.create_index(op.f('ix_a76_pedimento_decrementables_tenant_id'), 'pedimento_decrementables', ['tenant_id'], unique=False, schema='a76')
op.create_table('pedimento_incrementables',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('pedimento_id', sa.Integer(), nullable=False),
sa.Column('tenant_id', sa.Integer(), nullable=False),
sa.Column('insured_value', sa.Numeric(precision=13, scale=2), nullable=True),
sa.Column('freight', sa.Numeric(precision=13, scale=2), nullable=True),
sa.Column('insurance', sa.Numeric(precision=13, scale=2), nullable=True),
sa.Column('packaging', sa.Numeric(precision=13, scale=2), nullable=True),
sa.Column('others', sa.Numeric(precision=13, scale=3), nullable=True),
sa.Column('deductibles', sa.Numeric(precision=13, scale=3), nullable=True),
sa.Column('currency', sa.String(length=3), nullable=True),
sa.Column('currency_factor', sa.Numeric(precision=15, scale=8), nullable=True),
sa.Column('not_affect_usd_value', sa.SmallInteger(), nullable=True),
sa.Column('not_affect_customs_value', sa.SmallInteger(), nullable=True),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True),
sa.ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], name='fk_pedimento_incrementables', ondelete='CASCADE'),
sa.ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], ),
sa.PrimaryKeyConstraint('id', name='pedimento_incrementables_pkey'),
sa.UniqueConstraint('pedimento_id', name='pedimento_incrementables_pedimento_id_key'),
schema='a76'
)
op.create_index(op.f('ix_a76_pedimento_incrementables_tenant_id'), 'pedimento_incrementables', ['tenant_id'], unique=False, schema='a76')
op.create_table('pedimento_indexes',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('pedimento_id', sa.Integer(), nullable=False),
sa.Column('tenant_id', sa.Integer(), nullable=False),
sa.Column('update_factor_type', sa.SmallInteger(), nullable=True),
sa.Column('update_factor', sa.Numeric(precision=7, scale=4), nullable=True),
sa.Column('manual_update_factor', sa.SmallInteger(), nullable=True),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True),
sa.ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], name='fk_pedimento_indexes', ondelete='CASCADE'),
sa.ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], ),
sa.PrimaryKeyConstraint('id', name='pedimento_indexes_pkey'),
sa.UniqueConstraint('pedimento_id', name='pedimento_indexes_pedimento_id_key'),
schema='a76'
)
op.create_index(op.f('ix_a76_pedimento_indexes_tenant_id'), 'pedimento_indexes', ['tenant_id'], unique=False, schema='a76')
op.create_table('pedimento_payments',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('pedimento_id', sa.Integer(), nullable=False),
sa.Column('tenant_id', sa.Integer(), nullable=False),
sa.Column('acknowledgment', sa.String(length=20), nullable=True),
sa.Column('operation_number', sa.String(length=14), nullable=True),
sa.Column('bank_code', sa.Integer(), nullable=True),
sa.Column('cashier', sa.String(length=2), nullable=True),
sa.Column('date', sa.Date(), nullable=True),
sa.Column('time', sa.Time(), nullable=True),
sa.Column('shift', sa.String(length=1), nullable=True),
sa.Column('total_cash_paid', sa.Integer(), nullable=True),
sa.Column('total_contributions', sa.Integer(), nullable=True),
sa.Column('counter_payment', sa.SmallInteger(), nullable=True),
sa.Column('pece_code', sa.String(length=5), nullable=True),
sa.Column('payment_id', sa.Integer(), nullable=True),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True),
sa.ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], name='fk_pedimento_payments', ondelete='CASCADE'),
sa.ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], ),
sa.PrimaryKeyConstraint('id', name='pedimento_payments_pkey'),
sa.UniqueConstraint('pedimento_id', name='pedimento_payments_pedimento_id_key'),
schema='a76'
)
op.create_index('idx_pedimento_payments_pedimento_id', 'pedimento_payments', ['pedimento_id'], unique=False, schema='a76')
op.create_index(op.f('ix_a76_pedimento_payments_tenant_id'), 'pedimento_payments', ['tenant_id'], unique=False, schema='a76')
op.create_table('pedimento_rectification_destination',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('pedimento_id', sa.Integer(), nullable=False),
sa.Column('tenant_id', sa.Integer(), nullable=False),
sa.Column('destination_pedimento_year', sa.String(length=2), nullable=True),
sa.Column('destination_customs_office', sa.String(length=3), nullable=True),
sa.Column('destination_license', sa.String(length=4), nullable=True),
sa.Column('destination_pedimento_number', sa.String(length=7), nullable=True),
sa.ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], name='fk_pedimento_rectification_destination', ondelete='CASCADE'),
sa.ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], ),
sa.PrimaryKeyConstraint('id', name='pedimento_rectification_destination_pkey'),
sa.UniqueConstraint('pedimento_id', name='pedimento_rectification_destination_pedimento_id_key'),
schema='a76'
)
op.create_index(op.f('ix_a76_pedimento_rectification_destination_tenant_id'), 'pedimento_rectification_destination', ['tenant_id'], unique=False, schema='a76')
op.create_table('pedimento_rectification_origin',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('pedimento_id', sa.Integer(), nullable=False),
sa.Column('tenant_id', sa.Integer(), nullable=False),
sa.Column('original_pedimento_year', sa.String(length=2), nullable=True),
sa.Column('original_customs_office', sa.String(length=3), nullable=True),
sa.Column('original_license', sa.String(length=4), nullable=True),
sa.Column('original_pedimento_number', sa.String(length=7), nullable=True),
sa.Column('original_pedimento_key', sa.String(length=2), nullable=True),
sa.Column('original_payment_date', sa.DateTime(), nullable=True),
sa.Column('total_cash', sa.Integer(), nullable=True),
sa.Column('total_others', sa.Integer(), nullable=True),
sa.Column('reason', sa.String(length=255), nullable=True),
sa.Column('charge_to_client', sa.SmallInteger(), nullable=True),
sa.Column('use_original_payment_date_for_interest_calc', sa.SmallInteger(), nullable=True),
sa.Column('manual_calculation', sa.SmallInteger(), nullable=True),
sa.Column('original_pedimento_norms', sa.SmallInteger(), nullable=True),
sa.ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], name='fk_pedimento_rectification_origin', ondelete='CASCADE'),
sa.ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], ),
sa.PrimaryKeyConstraint('id', name='pedimento_rectification_origin_pkey'),
sa.UniqueConstraint('pedimento_id', name='pedimento_rectification_origin_pedimento_id_key'),
schema='a76'
)
op.create_index(op.f('ix_a76_pedimento_rectification_origin_tenant_id'), 'pedimento_rectification_origin', ['tenant_id'], unique=False, schema='a76')
op.create_table('pedimento_transport_means',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('pedimento_id', sa.Integer(), nullable=False),
sa.Column('tenant_id', sa.Integer(), nullable=False),
sa.Column('destination', sa.SmallInteger(), nullable=True),
sa.Column('entry_exit', sa.String(length=2), nullable=True),
sa.Column('arrival', sa.String(length=2), nullable=True),
sa.Column('departure', sa.String(length=2), nullable=True),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True),
sa.ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], name='fk_pedimento_transport_means', ondelete='CASCADE'),
sa.ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], ),
sa.PrimaryKeyConstraint('id', name='pedimento_transport_means_pkey'),
sa.UniqueConstraint('pedimento_id', name='pedimento_transport_means_pedimento_id_key'),
schema='a76'
)
op.create_index(op.f('ix_a76_pedimento_transport_means_tenant_id'), 'pedimento_transport_means', ['tenant_id'], unique=False, schema='a76')
op.create_table('pedimento_validation',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('pedimento_id', sa.Integer(), nullable=False),
sa.Column('tenant_id', sa.Integer(), nullable=False),
sa.Column('validator', sa.String(length=3), nullable=True),
sa.Column('validation_ack', sa.String(length=8), nullable=True),
sa.Column('pre_ack', sa.String(length=8), nullable=True),
sa.Column('line_signature', sa.String(length=50), nullable=True),
sa.Column('electronic_signature', sa.String(length=999), nullable=True),
sa.Column('certificate_number', sa.String(length=99), nullable=True),
sa.Column('validator_id', sa.Integer(), nullable=True),
sa.Column('responsible_id', sa.Integer(), nullable=True),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True),
sa.ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], name='fk_pedimento_validation', ondelete='CASCADE'),
sa.ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], ),
sa.PrimaryKeyConstraint('id', name='pedimento_validation_pkey'),
sa.UniqueConstraint('pedimento_id', name='pedimento_validation_pedimento_id_key'),
schema='a76'
)
op.create_index(op.f('ix_a76_pedimento_validation_tenant_id'), 'pedimento_validation', ['tenant_id'], unique=False, schema='a76')
op.drop_constraint(op.f('fk_regimenped'), 'code_pedimento_regimens', type_='foreignkey')
op.drop_constraint(op.f('fk_codeped'), 'code_pedimento_regimens', type_='foreignkey')
op.create_foreign_key('fk_codeped', 'code_pedimento_regimens', 'pedimento_codes', ['pedimento_code'], ['code'], source_schema='public', referent_schema='public')
op.create_foreign_key('fk_regimenped', 'code_pedimento_regimens', 'pedimento_regimens', ['regimen_code'], ['code'], source_schema='public', referent_schema='public')
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint('fk_regimenped', 'code_pedimento_regimens', schema='public', type_='foreignkey')
op.drop_constraint('fk_codeped', 'code_pedimento_regimens', schema='public', type_='foreignkey')
op.create_foreign_key(op.f('fk_codeped'), 'code_pedimento_regimens', 'pedimento_codes', ['pedimento_code'], ['code'])
op.create_foreign_key(op.f('fk_regimenped'), 'code_pedimento_regimens', 'pedimento_regimens', ['regimen_code'], ['code'])
op.drop_index(op.f('ix_a76_pedimento_validation_tenant_id'), table_name='pedimento_validation', schema='a76')
op.drop_table('pedimento_validation', schema='a76')
op.drop_index(op.f('ix_a76_pedimento_transport_means_tenant_id'), table_name='pedimento_transport_means', schema='a76')
op.drop_table('pedimento_transport_means', schema='a76')
op.drop_index(op.f('ix_a76_pedimento_rectification_origin_tenant_id'), table_name='pedimento_rectification_origin', schema='a76')
op.drop_table('pedimento_rectification_origin', schema='a76')
op.drop_index(op.f('ix_a76_pedimento_rectification_destination_tenant_id'), table_name='pedimento_rectification_destination', schema='a76')
op.drop_table('pedimento_rectification_destination', schema='a76')
op.drop_index(op.f('ix_a76_pedimento_payments_tenant_id'), table_name='pedimento_payments', schema='a76')
op.drop_index('idx_pedimento_payments_pedimento_id', table_name='pedimento_payments', schema='a76')
op.drop_table('pedimento_payments', schema='a76')
op.drop_index(op.f('ix_a76_pedimento_indexes_tenant_id'), table_name='pedimento_indexes', schema='a76')
op.drop_table('pedimento_indexes', schema='a76')
op.drop_index(op.f('ix_a76_pedimento_incrementables_tenant_id'), table_name='pedimento_incrementables', schema='a76')
op.drop_table('pedimento_incrementables', schema='a76')
op.drop_index(op.f('ix_a76_pedimento_decrementables_tenant_id'), table_name='pedimento_decrementables', schema='a76')
op.drop_table('pedimento_decrementables', schema='a76')
op.drop_index(op.f('ix_a76_pedimento_dates_tenant_id'), table_name='pedimento_dates', schema='a76')
op.drop_index('idx_pedimento_dates_pedimento_id', table_name='pedimento_dates', schema='a76')
op.drop_table('pedimento_dates', schema='a76')
op.drop_index(op.f('ix_a76_pedimento_customs_offices_tenant_id'), table_name='pedimento_customs_offices', schema='a76')
op.drop_table('pedimento_customs_offices', schema='a76')
op.drop_index(op.f('ix_a76_pedimento_config_updates_tenant_id'), table_name='pedimento_config_updates', schema='a76')
op.drop_table('pedimento_config_updates', schema='a76')
op.drop_index(op.f('ix_a76_pedimento_config_update_rectification_tenant_id'), table_name='pedimento_config_update_rectification', schema='a76')
op.drop_table('pedimento_config_update_rectification', schema='a76')
op.drop_index(op.f('ix_a76_pedimento_config_surcharges_tenant_id'), table_name='pedimento_config_surcharges', schema='a76')
op.drop_table('pedimento_config_surcharges', schema='a76')
op.drop_index(op.f('ix_a76_pedimento_config_parameters_tenant_id'), table_name='pedimento_config_parameters', schema='a76')
op.drop_table('pedimento_config_parameters', schema='a76')
op.drop_index(op.f('ix_a76_pedimento_config_calculations_tenant_id'), table_name='pedimento_config_calculations', schema='a76')
op.drop_table('pedimento_config_calculations', schema='a76')
op.drop_index(op.f('ix_a76_pedimento_config_additional_tenant_id'), table_name='pedimento_config_additional', schema='a76')
op.drop_table('pedimento_config_additional', schema='a76')
op.drop_index(op.f('ix_a76_pedimentos_tenant_id'), table_name='pedimentos', schema='a76')
op.drop_index('idx_pedimentos_status', table_name='pedimentos', schema='a76')
op.drop_index('idx_pedimentos_created_at', table_name='pedimentos', schema='a76')
op.drop_index('idx_pedimentos_client_id', table_name='pedimentos', schema='a76')
op.drop_table('pedimentos', schema='a76')
op.drop_index(op.f('ix_a76_licenses_tenant_id'), table_name='licenses', schema='a76')
op.drop_index(op.f('ix_a76_licenses_id'), table_name='licenses', schema='a76')
op.drop_table('licenses', schema='a76')
op.drop_index(op.f('ix_a76_license_usage_tenant_id'), table_name='license_usage', schema='a76')
op.drop_index(op.f('ix_a76_license_usage_id'), table_name='license_usage', schema='a76')
op.drop_table('license_usage', schema='a76')
op.drop_index(op.f('ix_a76_tenants_slug'), table_name='tenants', schema='a76')
op.drop_index(op.f('ix_a76_tenants_name'), table_name='tenants', schema='a76')
op.drop_index(op.f('ix_a76_tenants_id'), table_name='tenants', schema='a76')
op.drop_table('tenants', schema='a76')
# ### end Alembic commands ###

View File

@@ -0,0 +1,195 @@
"""Create new A76 tables only - company, clients, parts, classes
Revision ID: 54f2046774d0
Revises: 7937209f9718
Create Date: 2025-11-06 03:38:27.848630
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '54f2046774d0'
down_revision: Union[str, Sequence[str], None] = '7937209f9718'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema - Create only new A76 tables."""
# Create new A76 tables only (skip existing tenants, licenses, license_usage)
op.create_table('client_provider',
sa.Column('client_id', sa.String(length=8), nullable=False),
sa.Column('type_nat_foreign', sa.String(length=1), nullable=True),
sa.Column('name', sa.String(length=256), nullable=True),
sa.Column('short_name', sa.String(length=10), nullable=True),
sa.Column('rfc', sa.String(length=30), nullable=True),
sa.Column('curp', sa.String(length=19), nullable=True),
sa.Column('client_or_provider', sa.String(length=1), nullable=True),
sa.Column('linking', sa.String(length=1), nullable=True),
sa.Column('transform_subassembly', sa.String(length=1), nullable=True),
sa.Column('extra_information', sa.String(length=399), nullable=True),
sa.Column('web_key', sa.String(length=40), nullable=True),
sa.Column('responsible', sa.String(length=80), nullable=True),
sa.Column('position', sa.String(length=30), nullable=True),
sa.Column('incoterm', sa.String(length=19), nullable=True),
sa.Column('is_national_provider', sa.String(length=2), nullable=True),
sa.Column('enabled_disabled', sa.SmallInteger(), nullable=True),
sa.PrimaryKeyConstraint('client_id'),
schema='a76'
)
op.create_table('gcompany',
sa.Column('id', sa.String(length=3), nullable=False),
sa.Column('consecutive', sa.Boolean(), nullable=False),
sa.Column('name', sa.String(length=255), nullable=True),
sa.Column('rfc', sa.String(length=30), nullable=True),
sa.Column('main_activity', sa.String(length=255), nullable=True),
sa.Column('program', sa.String(length=10), nullable=True),
sa.Column('program_number', sa.String(length=40), nullable=True),
sa.Column('prosec', sa.SmallInteger(), nullable=True),
sa.Column('prosec_authorization', sa.String(length=20), nullable=True),
sa.Column('manufacturer_id', sa.String(length=25), nullable=True),
sa.Column('broker_company', sa.String(length=10), nullable=True),
sa.Column('responsible', sa.String(length=80), nullable=True),
sa.Column('responsible_name', sa.String(length=20), nullable=True),
sa.Column('responsible_last_name', sa.String(length=20), nullable=True),
sa.Column('responsible_mother_last_name', sa.String(length=20), nullable=True),
sa.Column('responsible_rfc', sa.String(length=30), nullable=True),
sa.Column('position', sa.String(length=30), nullable=True),
sa.Column('logo', sa.String(length=255), nullable=True),
sa.Column('has_express_line', sa.Boolean(), nullable=True),
sa.Column('order_format_type', sa.String(length=19), nullable=True),
sa.Column('previous_code', sa.SmallInteger(), nullable=True),
sa.Column('is_service_company', sa.Boolean(), nullable=True),
sa.Column('client_name', sa.String(length=300), nullable=True),
sa.Column('subassembly_mode', sa.String(length=7), nullable=True),
sa.Column('curp', sa.String(length=19), nullable=True),
sa.Column('inter_db_name', sa.String(length=100), nullable=True),
sa.Column('ctpat_svi', sa.String(length=100), nullable=True),
sa.Column('trusted_exporter_number', sa.String(length=50), nullable=True),
sa.Column('prevalidator_key', sa.String(length=20), nullable=True),
sa.Column('seventh_amendment', sa.Boolean(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('consecutive'),
schema='a76'
)
op.create_table('classes',
sa.Column('client_key', sa.Integer(), nullable=False),
sa.Column('class_code', sa.String(length=8), nullable=False),
sa.Column('description_spanish', sa.String(length=500), nullable=True),
sa.Column('description_english', sa.String(length=500), nullable=True),
sa.Column('material_key', sa.String(length=10), nullable=True),
sa.Column('unit_of_measure', sa.String(length=5), nullable=True),
sa.Column('fraction', sa.String(length=10), nullable=True),
sa.Column('us_fraction', sa.String(length=16), nullable=True),
sa.Column('sub_key', sa.String(length=5), nullable=True),
sa.Column('physical_review', sa.SmallInteger(), nullable=True),
sa.Column('iva_exempt_fraction', sa.String(length=4), nullable=True),
sa.ForeignKeyConstraint(['material_key'], ['public.material_types.key'], ),
sa.PrimaryKeyConstraint('client_key', 'class_code'),
schema='a76'
)
op.create_table('parts',
sa.Column('client_key', sa.Integer(), nullable=False),
sa.Column('part_number', sa.String(length=49), nullable=False),
sa.Column('fraction', sa.String(length=10), nullable=True),
sa.Column('description_spanish', sa.String(length=500), nullable=True),
sa.Column('description_english', sa.String(length=500), nullable=True),
sa.Column('part_class', sa.String(length=8), nullable=True),
sa.Column('unit_of_measure', sa.String(length=5), nullable=True),
sa.Column('commercial_part_number', sa.String(length=70), nullable=True),
sa.Column('country_of_origin', sa.String(length=3), nullable=True),
sa.Column('unit_cost', sa.Numeric(precision=23, scale=8), nullable=True),
sa.Column('currency_type', sa.String(length=2), nullable=True),
sa.Column('currency_key', sa.String(length=3), nullable=True),
sa.Column('unit_weight', sa.Numeric(precision=19, scale=8), nullable=True),
sa.Column('weight_type', sa.String(length=6), nullable=True),
sa.Column('us_fraction', sa.String(length=16), nullable=True),
sa.Column('fda_key', sa.String(length=20), nullable=True),
sa.Column('fcc_key', sa.String(length=30), nullable=True),
sa.Column('license_code', sa.String(length=3), nullable=True),
sa.Column('eccn', sa.String(length=20), nullable=True),
sa.Column('export_code', sa.String(length=2), nullable=True),
sa.Column('exclusion_symbol', sa.String(length=19), nullable=True),
sa.Column('supplier', sa.String(length=14), nullable=True),
sa.Column('alternate_unit_measure', sa.String(length=14), nullable=True),
sa.Column('added_value', sa.Numeric(precision=23, scale=8), nullable=True),
sa.Column('enabled_disabled', sa.SmallInteger(), nullable=True),
sa.Column('creation_date', sa.Integer(), nullable=True),
sa.Column('modification_date', sa.Integer(), nullable=True),
sa.Column('modification_date_iso', sa.DateTime(timezone=True), nullable=True),
sa.Column('part_photo', sa.String(length=255), nullable=True),
sa.ForeignKeyConstraint(['country_of_origin'], ['public.countries.m3_key'], ),
sa.ForeignKeyConstraint(['currency_key'], ['public.currency_types.code'], ),
sa.PrimaryKeyConstraint('client_key', 'part_number'),
schema='a76'
)
# Create dependent tables after main tables
op.create_table('gclient_provider_address',
sa.Column('client_id', sa.String(length=8), nullable=False),
sa.Column('municipality', sa.String(length=150), nullable=True),
sa.Column('streets', sa.String(length=100), nullable=True),
sa.Column('neighborhood', sa.String(length=40), nullable=True),
sa.Column('interior_number', sa.String(length=20), nullable=True),
sa.Column('exterior_number', sa.String(length=20), nullable=True),
sa.Column('postal_code', sa.String(length=15), nullable=True),
sa.Column('city', sa.String(length=30), nullable=True),
sa.Column('state', sa.String(length=30), nullable=True),
sa.Column('country', sa.String(length=3), nullable=True),
sa.Column('phone', sa.String(length=30), nullable=True),
sa.Column('fax_number', sa.String(length=30), nullable=True),
sa.Column('email', sa.String(length=100), nullable=True),
sa.Column('contact', sa.String(length=50), nullable=True),
sa.Column('reference', sa.String(length=250), nullable=True),
sa.ForeignKeyConstraint(['client_id'], ['a76.client_provider.client_id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('client_id'),
schema='a76'
)
op.create_table('gclient_provider_programs',
sa.Column('client_id', sa.String(length=8), nullable=False),
sa.Column('program', sa.String(length=7), nullable=True),
sa.Column('program_number', sa.String(length=40), nullable=True),
sa.Column('prosec', sa.SmallInteger(), nullable=True),
sa.Column('prosec_authorization', sa.String(length=20), nullable=True),
sa.Column('secon_auth_date', sa.Integer(), nullable=True),
sa.Column('manufacturer_id', sa.String(length=25), nullable=True),
sa.Column('tax_id', sa.String(length=30), nullable=True),
sa.Column('broker', sa.String(length=6), nullable=True),
sa.Column('import_broker', sa.String(length=6), nullable=True),
sa.Column('transfer_key', sa.String(length=8), nullable=True),
sa.Column('secon_authorization', sa.String(length=20), nullable=True),
sa.Column('applied_proportion', sa.Numeric(precision=7, scale=2), nullable=True),
sa.Column('is_certified_company', sa.String(length=1), nullable=True),
sa.Column('certified_company_registry', sa.String(length=40), nullable=True),
sa.Column('donation_auth_number', sa.String(length=50), nullable=True),
sa.Column('ctpat_svi', sa.String(length=100), nullable=True),
sa.Column('tax_registry_number', sa.String(length=40), nullable=True),
sa.Column('subassembly_service', sa.SmallInteger(), nullable=True),
sa.Column('autse_dates', sa.Integer(), nullable=True),
sa.Column('autse_number', sa.String(length=300), nullable=True),
sa.ForeignKeyConstraint(['client_id'], ['a76.client_provider.client_id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('client_id'),
schema='a76'
)
def downgrade() -> None:
"""Downgrade schema - Drop only new A76 tables."""
# Drop tables in reverse dependency order
op.drop_table('gclient_provider_programs', schema='a76')
op.drop_table('gclient_provider_address', schema='a76')
op.drop_table('parts', schema='a76')
op.drop_table('classes', schema='a76')
op.drop_table('gcompany', schema='a76')
op.drop_table('client_provider', schema='a76')

View File

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

View File

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

View File

@@ -0,0 +1,61 @@
"""
Modelos ORM para gestión de clases SCAII y SCAF
"""
from sqlalchemy import Column, Integer, String, DateTime, Boolean, Text, Numeric, SmallInteger, ForeignKey
from sqlalchemy.sql import func
from sqlalchemy.orm import relationship
from core.database import Base
import enum
# Importar modelos relacionados para type hints y relationships
from typing import TYPE_CHECKING, List, Optional
if TYPE_CHECKING:
from api.v1.modules.a76.parts.models import Part
from api.v1.modules.public.reference_data.material_types.models import MaterialType
class Class(Base):
"""
Modelo para la tabla GClases - Información de clases en sistemas SCAII y SCAF
"""
__tablename__ = "classes"
__table_args__ = {"schema": "a76"}
# Primary key compuesta
client_key = Column(Integer, primary_key=True, nullable=False)
class_code = Column(String(8), primary_key=True, nullable=False)
# Basic information
description_spanish = Column(String(500), nullable=True)
description_english = Column(String(500), nullable=True)
# Material and measurement
material_key = Column(String(10), ForeignKey('public.material_types.key'), nullable=True) # CLAVEMAT - homologated from TIPOMAT/TIPOMATEQUIPO
unit_of_measure = Column(String(5), nullable=True) # UNIMED - homologated from UNIMEDIDA
# Tariff fractions
fraction = Column(String(10), nullable=True) # Mexican tariff fraction
us_fraction = Column(String(16), nullable=True) # FRACCIONAME - US tariff fraction
# Additional classification
sub_key = Column(String(5), nullable=True) # CLAVESUB
physical_review = Column(SmallInteger, nullable=True) # REVFISICA
iva_exempt_fraction = Column(String(4), nullable=True) # FRACCIONEXENTAIVA
# Relationships
material_type = relationship("MaterialType", foreign_keys=[material_key])
# Inverse relationship with GParts that have this class
parts = relationship(
"Part",
primaryjoin="and_(Class.client_key == Part.client_key, Class.class_code == Part.part_class)",
foreign_keys="[Part.client_key, Part.part_class]",
viewonly=True,
back_populates="part_class_info"
)
def __repr__(self):
return f"<Class(client_key={self.client_key}, class_code='{self.class_code}', description='{self.description_spanish}')>"

View File

@@ -0,0 +1,261 @@
"""
Endpoints API para gestión de clases SCAII y SCAF
"""
from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy.orm import Session
from typing import List, Optional
from core.database import get_core_db
from core.security import get_current_user, has_role
from .service import ClassService
from .dto import (
ClassCreateDTO,
ClassUpdateDTO,
ClassResponseDTO,
ClassBasicDTO,
ClassListDTO,
ClassSearchDTO
)
router = APIRouter(prefix="/classes", tags=["Classes"])
@router.post("/", response_model=ClassResponseDTO, status_code=status.HTTP_201_CREATED)
async def create_class(
class_data: ClassCreateDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Create a new class in the system
"""
service = ClassService(db)
return service.create_class(class_data)
@router.get("/", response_model=ClassListDTO)
async def list_classes(
skip: int = Query(0, ge=0, description="Number of records to skip"),
limit: int = Query(100, ge=1, le=1000, description="Maximum number of records to return"),
client_key: Optional[int] = Query(None, description="Filter by client key"),
class_code: Optional[str] = Query(None, description="Search by class code"),
description: Optional[str] = Query(None, description="Search in descriptions"),
material_key: Optional[str] = Query(None, description="Filter by material key"),
fraction: Optional[str] = Query(None, description="Filter by tariff fraction"),
physical_review: Optional[int] = Query(None, description="Filter by physical review indicator"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
List classes with optional filters and pagination
"""
service = ClassService(db)
search_params = ClassSearchDTO(
client_key=client_key,
class_code=class_code,
description=description,
material_key=material_key,
fraction=fraction,
physical_review=physical_review
)
return service.list_classes(skip, limit, search_params)
@router.get("/client/{client_key}", response_model=List[ClassBasicDTO])
async def get_classes_by_client(
client_key: int,
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Get all classes for a specific client
"""
service = ClassService(db)
return service.search_by_client(client_key, skip, limit)
@router.get("/search/fraction/{fraction}", response_model=List[ClassBasicDTO])
async def search_by_fraction(
fraction: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Search classes by tariff fraction
"""
service = ClassService(db)
return service.search_by_fraction(fraction)
@router.get("/search/material/{material_key}", response_model=List[ClassBasicDTO])
async def search_by_material(
material_key: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Search classes by material key
"""
service = ClassService(db)
return service.search_by_material(material_key)
@router.get("/search/unit-measure/{unit_of_measure}", response_model=List[ClassBasicDTO])
async def get_classes_by_unit_measure(
unit_of_measure: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Get classes by unit of measure
"""
service = ClassService(db)
return service.get_classes_by_unit_measure(unit_of_measure)
@router.get("/search/physical-review/{physical_review}", response_model=List[ClassBasicDTO])
async def get_classes_by_physical_review(
physical_review: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Get classes by physical review indicator
"""
service = ClassService(db)
return service.get_classes_by_physical_review(physical_review)
@router.get("/statistics", response_model=dict)
async def get_classes_statistics(
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Get basic classes statistics
"""
service = ClassService(db)
return service.get_classes_statistics()
@router.get("/{client_key}/{class_code}", response_model=ClassResponseDTO)
async def get_class(
client_key: int,
class_code: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Get class by composite key (client_key + class_code)
"""
service = ClassService(db)
class_obj = service.get_class(client_key, class_code)
if not class_obj:
raise HTTPException(
status_code=404,
detail=f"Class with client_key '{client_key}' and class_code '{class_code}' not found"
)
return class_obj
@router.put("/{client_key}/{class_code}", response_model=ClassResponseDTO)
async def update_class(
client_key: int,
class_code: str,
class_data: ClassUpdateDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Update class information
"""
service = ClassService(db)
class_obj = service.update_class(client_key, class_code, class_data)
if not class_obj:
raise HTTPException(
status_code=404,
detail=f"Class with client_key '{client_key}' and class_code '{class_code}' not found"
)
return class_obj
@router.delete("/{client_key}/{class_code}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_class(
client_key: int,
class_code: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Delete class from the system
Note: This will completely remove the class from the system.
"""
service = ClassService(db)
if not service.delete_class(client_key, class_code):
raise HTTPException(
status_code=404,
detail=f"Class with client_key '{client_key}' and class_code '{class_code}' not found"
)
# Endpoints específicos para información detallada
@router.get("/{client_key}/{class_code}/basic", response_model=ClassBasicDTO)
async def get_class_basic_info(
client_key: int,
class_code: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Get basic information for a class
"""
service = ClassService(db)
class_obj = service.get_class(client_key, class_code)
if not class_obj:
raise HTTPException(
status_code=404,
detail=f"Class with client_key '{client_key}' and class_code '{class_code}' not found"
)
return ClassBasicDTO(
client_key=class_obj.client_key,
class_code=class_obj.class_code,
description_spanish=class_obj.description_spanish,
description_english=class_obj.description_english,
material_key=class_obj.material_key,
fraction=class_obj.fraction
)
@router.get("/{client_key}/{class_code}/tariff", response_model=dict)
async def get_class_tariff_info(
client_key: int,
class_code: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Get tariff information for a class (fractions, IVA exempt, etc.)
"""
service = ClassService(db)
class_obj = service.get_class(client_key, class_code)
if not class_obj:
raise HTTPException(
status_code=404,
detail=f"Class with client_key '{client_key}' and class_code '{class_code}' not found"
)
return {
"client_key": class_obj.client_key,
"class_code": class_obj.class_code,
"fraction": class_obj.fraction,
"us_fraction": class_obj.us_fraction,
"iva_exempt_fraction": class_obj.iva_exempt_fraction,
"sub_key": class_obj.sub_key,
"physical_review": class_obj.physical_review
}

View File

@@ -0,0 +1,294 @@
"""
Capa de servicio para lógica de negocio de clases SCAII y SCAF
"""
from sqlalchemy.orm import Session
from sqlalchemy.exc import IntegrityError
from sqlalchemy import or_, and_, func
from fastapi import HTTPException
from typing import List, Optional
import logging
from .models import Class
from .dto import (
ClassCreateDTO,
ClassUpdateDTO,
ClassResponseDTO,
ClassBasicDTO,
ClassListDTO,
ClassSearchDTO
)
logger = logging.getLogger(__name__)
class ClassService:
"""Servicio para gestión de clases SCAII y SCAF"""
def __init__(self, db: Session):
self.db = db
def create_class(self, class_data: ClassCreateDTO) -> ClassResponseDTO:
"""
Crea una nueva clase en el sistema
Args:
class_data: Datos de la clase a crear
Returns:
ClassResponseDTO con información de la clase creada
Raises:
HTTPException: Si la clase ya existe o error en la creación
"""
try:
# Verificar que no exista la clase
existing = self.db.query(Class).filter(
and_(
Class.client_key == class_data.client_key,
Class.class_code == class_data.class_code
)
).first()
if existing:
raise HTTPException(
status_code=400,
detail=f"Class with client_key '{class_data.client_key}' and class_code '{class_data.class_code}' already exists"
)
# Crear clase
db_class = Class(
client_key=class_data.client_key,
class_code=class_data.class_code,
description_spanish=class_data.description_spanish,
description_english=class_data.description_english,
material_key=class_data.material_key,
unit_of_measure=class_data.unit_of_measure,
fraction=class_data.fraction,
us_fraction=class_data.us_fraction,
sub_key=class_data.sub_key,
physical_review=class_data.physical_review,
iva_exempt_fraction=class_data.iva_exempt_fraction
)
self.db.add(db_class)
self.db.commit()
self.db.refresh(db_class)
logger.info(f"Class created: {db_class.client_key}-{db_class.class_code}")
return ClassResponseDTO.model_validate(db_class)
except IntegrityError as e:
self.db.rollback()
logger.error(f"IntegrityError creating class: {str(e)}")
raise HTTPException(status_code=400, detail="Class with this client_key and class_code already exists")
except HTTPException:
raise
except Exception as e:
self.db.rollback()
logger.error(f"Error creating class: {str(e)}")
raise HTTPException(status_code=500, detail="Error creating class")
def get_class(self, client_key: int, class_code: str) -> Optional[ClassResponseDTO]:
"""
Obtiene una clase por clave compuesta
Args:
client_key: Clave del cliente
class_code: Código de clase
Returns:
ClassResponseDTO o None si no existe
"""
class_obj = self.db.query(Class).filter(
and_(
Class.client_key == client_key,
Class.class_code == class_code
)
).first()
if not class_obj:
return None
return ClassResponseDTO.model_validate(class_obj)
def list_classes(
self,
skip: int = 0,
limit: int = 100,
search_params: Optional[ClassSearchDTO] = None
) -> ClassListDTO:
"""
Lista clases con filtros
Args:
skip: Número de registros a omitir
limit: Número máximo de registros a retornar
search_params: Parámetros de búsqueda
Returns:
ClassListDTO con la lista paginada
"""
query = self.db.query(Class)
# Aplicar filtros si se proporcionan
if search_params:
if search_params.client_key:
query = query.filter(Class.client_key == search_params.client_key)
if search_params.class_code:
query = query.filter(Class.class_code.ilike(f"%{search_params.class_code}%"))
if search_params.description:
description_pattern = f"%{search_params.description}%"
query = query.filter(
or_(
Class.description_spanish.ilike(description_pattern),
Class.description_english.ilike(description_pattern)
)
)
if search_params.material_key:
query = query.filter(Class.material_key.ilike(f"%{search_params.material_key}%"))
if search_params.fraction:
query = query.filter(Class.fraction.ilike(f"%{search_params.fraction}%"))
if search_params.physical_review is not None:
query = query.filter(Class.physical_review == search_params.physical_review)
# Contar total
total = query.count()
# Aplicar paginación
classes = query.offset(skip).limit(limit).all()
# Convertir a DTOs básicos
class_dtos = [ClassBasicDTO.model_validate(class_obj) for class_obj in classes]
return ClassListDTO(
classes=class_dtos,
total=total,
page=(skip // limit) + 1 if limit > 0 else 1,
size=len(class_dtos)
)
def update_class(self, client_key: int, class_code: str, class_data: ClassUpdateDTO) -> Optional[ClassResponseDTO]:
"""
Actualiza una clase
Args:
client_key: Clave del cliente
class_code: Código de clase
class_data: Datos a actualizar
Returns:
ClassResponseDTO actualizado o None si no existe
"""
class_obj = self.db.query(Class).filter(
and_(
Class.client_key == client_key,
Class.class_code == class_code
)
).first()
if not class_obj:
return None
try:
# Actualizar solo campos proporcionados
update_data = class_data.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(class_obj, field, value)
self.db.commit()
self.db.refresh(class_obj)
logger.info(f"Class updated: {client_key}-{class_code}")
return ClassResponseDTO.model_validate(class_obj)
except Exception as e:
self.db.rollback()
logger.error(f"Error updating class {client_key}-{class_code}: {str(e)}")
raise HTTPException(status_code=500, detail="Error updating class")
def delete_class(self, client_key: int, class_code: str) -> bool:
"""
Elimina una clase
Args:
client_key: Clave del cliente
class_code: Código de clase
Returns:
True si se eliminó, False si no existe
"""
class_obj = self.db.query(Class).filter(
and_(
Class.client_key == client_key,
Class.class_code == class_code
)
).first()
if not class_obj:
return False
try:
self.db.delete(class_obj)
self.db.commit()
logger.info(f"Class deleted: {client_key}-{class_code}")
return True
except Exception as e:
self.db.rollback()
logger.error(f"Error deleting class {client_key}-{class_code}: {str(e)}")
raise HTTPException(status_code=500, detail="Error deleting class")
def search_by_fraction(self, fraction: str) -> List[ClassBasicDTO]:
"""Busca clases por fracción arancelaria"""
classes = self.db.query(Class).filter(Class.fraction.ilike(f"%{fraction}%")).all()
return [ClassBasicDTO.model_validate(class_obj) for class_obj in classes]
def search_by_client(self, client_key: int, skip: int = 0, limit: int = 100) -> List[ClassBasicDTO]:
"""Obtiene todas las clases de un cliente específico"""
classes = self.db.query(Class).filter(Class.client_key == client_key).offset(skip).limit(limit).all()
return [ClassBasicDTO.model_validate(class_obj) for class_obj in classes]
def search_by_material(self, material_key: str) -> List[ClassBasicDTO]:
"""Busca clases por clave de material"""
classes = self.db.query(Class).filter(Class.material_key.ilike(f"%{material_key}%")).all()
return [ClassBasicDTO.model_validate(class_obj) for class_obj in classes]
def get_classes_by_physical_review(self, physical_review: int) -> List[ClassBasicDTO]:
"""Obtiene clases por indicador de revisión física"""
classes = self.db.query(Class).filter(Class.physical_review == physical_review).all()
return [ClassBasicDTO.model_validate(class_obj) for class_obj in classes]
def get_classes_statistics(self) -> dict:
"""Obtiene estadísticas básicas de clases"""
total_classes = self.db.query(Class).count()
# Contar por clientes
clients_count = self.db.query(Class.client_key).distinct().count()
# Contar por revisión física
physical_review_stats = {}
for i in range(3): # Asumiendo valores 0, 1, 2
count = self.db.query(Class).filter(Class.physical_review == i).count()
physical_review_stats[f"physical_review_{i}"] = count
# Contar clases con fracciones
with_fraction = self.db.query(Class).filter(Class.fraction.isnot(None)).count()
with_us_fraction = self.db.query(Class).filter(Class.us_fraction.isnot(None)).count()
return {
"total_classes": total_classes,
"clients_with_classes": clients_count,
"classes_with_fraction": with_fraction,
"classes_with_us_fraction": with_us_fraction,
**physical_review_stats
}
def get_classes_by_unit_measure(self, unit_of_measure: str) -> List[ClassBasicDTO]:
"""Obtiene clases por unidad de medida"""
classes = self.db.query(Class).filter(Class.unit_of_measure == unit_of_measure).all()
return [ClassBasicDTO.model_validate(class_obj) for class_obj in classes]

View File

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

View File

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

View File

@@ -0,0 +1,108 @@
"""
Modelos ORM para gestión de clientes y proveedores
"""
from sqlalchemy import Column, Integer, String, DateTime, Boolean, Text, SmallInteger, Numeric, ForeignKey
from sqlalchemy.sql import func
from sqlalchemy.orm import relationship
from core.database import Base
import enum
class ClientProvider(Base):
"""
Modelo para la tabla GClientesPro - Información de clientes y proveedores
"""
__tablename__ = "client_provider"
__table_args__ = {"schema": "a76"}
# Primary key
client_id = Column(String(8), primary_key=True, nullable=False)
# Basic information
type_nat_foreign = Column(String(1), nullable=True) # TIPO NACIONAL/EXTRANJERO
name = Column(String(256), nullable=True)
short_name = Column(String(10), nullable=True)
rfc = Column(String(30), nullable=True)
curp = Column(String(19), nullable=True)
client_or_provider = Column(String(1), nullable=True)
linking = Column(String(1), nullable=True)
transform_subassembly = Column(String(1), nullable=True)
extra_information = Column(String(399), nullable=True)
web_key = Column(String(40), nullable=True)
responsible = Column(String(80), nullable=True)
position = Column(String(30), nullable=True)
incoterm = Column(String(19), nullable=True)
is_national_provider = Column(String(2), nullable=True)
enabled_disabled = Column(SmallInteger, nullable=True)
# Relationships
address = relationship("GClientProviderAddress", back_populates="client_provider", uselist=False, cascade="all, delete-orphan")
programs = relationship("GClientProviderPrograms", back_populates="client_provider", uselist=False, cascade="all, delete-orphan")
class GClientProviderAddress(Base):
"""
Modelo para la tabla GClientesPro_Direccion - Dirección de clientes y proveedores
"""
__tablename__ = "gclient_provider_address"
__table_args__ = {"schema": "a76"}
# Primary key (foreign key)
client_id = Column(String(8), ForeignKey('a76.client_provider.client_id', ondelete='CASCADE'), primary_key=True, nullable=False)
# Address information
municipality = Column(String(150), nullable=True)
streets = Column(String(100), nullable=True)
neighborhood = Column(String(40), nullable=True)
interior_number = Column(String(20), nullable=True)
exterior_number = Column(String(20), nullable=True)
postal_code = Column(String(15), nullable=True)
city = Column(String(30), nullable=True)
state = Column(String(30), nullable=True)
country = Column(String(3), nullable=True)
phone = Column(String(30), nullable=True)
fax_number = Column(String(30), nullable=True)
email = Column(String(100), nullable=True)
contact = Column(String(50), nullable=True)
reference = Column(String(250), nullable=True)
# Relationship
client_provider = relationship("ClientProvider", back_populates="address")
class GClientProviderPrograms(Base):
"""
Modelo para la tabla GClientesPro_Programas - Programas de clientes y proveedores
"""
__tablename__ = "gclient_provider_programs"
__table_args__ = {"schema": "a76"}
# Primary key (foreign key)
client_id = Column(String(8), ForeignKey('a76.client_provider.client_id', ondelete='CASCADE'), primary_key=True, nullable=False)
# Program information
program = Column(String(7), nullable=True)
program_number = Column(String(40), nullable=True)
prosec = Column(SmallInteger, nullable=True)
prosec_authorization = Column(String(20), nullable=True)
secon_auth_date = Column(Integer, nullable=True)
manufacturer_id = Column(String(25), nullable=True)
tax_id = Column(String(30), nullable=True)
broker = Column(String(6), nullable=True)
import_broker = Column(String(6), nullable=True)
transfer_key = Column(String(8), nullable=True)
secon_authorization = Column(String(20), nullable=True)
applied_proportion = Column(Numeric(7, 2), nullable=True)
is_certified_company = Column(String(1), nullable=True)
certified_company_registry = Column(String(40), nullable=True)
donation_auth_number = Column(String(50), nullable=True)
ctpat_svi = Column(String(100), nullable=True)
tax_registry_number = Column(String(40), nullable=True)
subassembly_service = Column(SmallInteger, nullable=True)
autse_dates = Column(Integer, nullable=True)
autse_number = Column(String(300), nullable=True)
# Relationship
client_provider = relationship("ClientProvider", back_populates="programs")

View File

@@ -0,0 +1,221 @@
"""
Endpoints API para gestión de clientes y proveedores
"""
from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy.orm import Session
from typing import List, Optional
from core.database import get_core_db
from core.security import get_current_user, has_role
from .service import ClientProviderService
from .dto import (
ClientProviderCreateDTO,
ClientProviderUpdateDTO,
ClientProviderResponseDTO,
ClientProviderBasicDTO,
ClientProviderListDTO
)
router = APIRouter(prefix="/clients-providers")
@router.post("/", response_model=ClientProviderResponseDTO, status_code=status.HTTP_201_CREATED)
async def create_client_provider(
client_data: ClientProviderCreateDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Create a new client or provider in the system
"""
service = ClientProviderService(db)
return service.create_client_provider(client_data)
@router.get("/", response_model=ClientProviderListDTO)
async def list_clients_providers(
skip: int = Query(0, ge=0, description="Number of records to skip"),
limit: int = Query(100, ge=1, le=1000, description="Maximum number of records to return"),
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"),
enabled_only: bool = Query(False, description="Show only enabled records"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
List clients and providers with optional filters and pagination
"""
service = ClientProviderService(db)
return service.list_clients_providers(skip, limit, search, client_or_provider, enabled_only)
@router.get("/clients", response_model=List[ClientProviderBasicDTO])
async def get_clients_only(
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Get only clients (client_or_provider = 'C')
"""
service = ClientProviderService(db)
return service.get_clients_only(skip, limit)
@router.get("/providers", response_model=List[ClientProviderBasicDTO])
async def get_providers_only(
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Get only providers (client_or_provider = 'P')
"""
service = ClientProviderService(db)
return service.get_providers_only(skip, limit)
@router.get("/search/rfc/{rfc}", response_model=List[ClientProviderBasicDTO])
async def search_by_rfc(
rfc: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Search clients/providers by RFC
"""
service = ClientProviderService(db)
return service.search_by_rfc(rfc)
@router.get("/{client_id}", response_model=ClientProviderResponseDTO)
async def get_client_provider(
client_id: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Get client/provider by ID with all related information
"""
service = ClientProviderService(db)
client = service.get_client_provider(client_id)
if not client:
raise HTTPException(status_code=404, detail=f"Client/Provider with ID '{client_id}' not found")
return client
@router.put("/{client_id}", response_model=ClientProviderResponseDTO)
async def update_client_provider(
client_id: str,
client_data: ClientProviderUpdateDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Update client/provider information
"""
service = ClientProviderService(db)
client = service.update_client_provider(client_id, client_data)
if not client:
raise HTTPException(status_code=404, detail=f"Client/Provider with ID '{client_id}' not found")
return client
@router.delete("/{client_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_client_provider(
client_id: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Delete client/provider from the system
Note: This will completely remove the client/provider and all related data.
"""
service = ClientProviderService(db)
if not service.delete_client_provider(client_id):
raise HTTPException(status_code=404, detail=f"Client/Provider with ID '{client_id}' not found")
@router.patch("/{client_id}/toggle-status", response_model=ClientProviderResponseDTO)
async def toggle_client_provider_status(
client_id: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Toggle client/provider enabled/disabled status
"""
service = ClientProviderService(db)
client = service.toggle_status(client_id)
if not client:
raise HTTPException(status_code=404, detail=f"Client/Provider with ID '{client_id}' not found")
return client
# Endpoints específicos para información detallada
@router.get("/{client_id}/address", response_model=dict)
async def get_client_provider_address(
client_id: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Get only address information for a client/provider
"""
service = ClientProviderService(db)
client = service.get_client_provider(client_id)
if not client:
raise HTTPException(status_code=404, detail=f"Client/Provider with ID '{client_id}' not found")
return {
"client_id": client.client_id,
"address": client.address
}
@router.get("/{client_id}/programs", response_model=dict)
async def get_client_provider_programs(
client_id: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Get only programs information for a client/provider
"""
service = ClientProviderService(db)
client = service.get_client_provider(client_id)
if not client:
raise HTTPException(status_code=404, detail=f"Client/Provider with ID '{client_id}' not found")
return {
"client_id": client.client_id,
"programs": client.programs
}
@router.get("/{client_id}/basic", response_model=ClientProviderBasicDTO)
async def get_client_provider_basic_info(
client_id: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Get basic information for a client/provider (without address and programs)
"""
service = ClientProviderService(db)
client = service.get_client_provider(client_id)
if not client:
raise HTTPException(status_code=404, detail=f"Client/Provider with ID '{client_id}' not found")
return ClientProviderBasicDTO(
client_id=client.client_id,
name=client.name,
short_name=client.short_name,
rfc=client.rfc,
client_or_provider=client.client_or_provider,
enabled_disabled=client.enabled_disabled
)

View File

@@ -0,0 +1,309 @@
"""
Capa de servicio para lógica de negocio de clientes y proveedores
"""
from sqlalchemy.orm import Session, joinedload
from sqlalchemy.exc import IntegrityError
from sqlalchemy import or_, and_
from fastapi import HTTPException
from typing import List, Optional
import logging
from .models import ClientProvider, GClientProviderAddress, GClientProviderPrograms
from .dto import (
ClientProviderCreateDTO,
ClientProviderUpdateDTO,
ClientProviderResponseDTO,
ClientProviderBasicDTO,
ClientProviderListDTO,
ClientProviderAddressDTO,
ClientProviderProgramsDTO
)
logger = logging.getLogger(__name__)
class ClientProviderService:
"""Servicio para gestión de clientes y proveedores"""
def __init__(self, db: Session):
self.db = db
def create_client_provider(self, client_data: ClientProviderCreateDTO) -> ClientProviderResponseDTO:
"""
Crea un nuevo cliente/proveedor en el sistema
Args:
client_data: Datos del cliente/proveedor a crear
Returns:
ClientProviderResponseDTO con información del cliente/proveedor creado
Raises:
HTTPException: Si el cliente ya existe o error en la creación
"""
try:
# Verificar que no exista el cliente
existing = self.db.query(ClientProvider).filter(ClientProvider.client_id == client_data.client_id).first()
if existing:
raise HTTPException(status_code=400, detail=f"Client with ID '{client_data.client_id}' already exists")
# Crear cliente/proveedor principal
db_client = ClientProvider(
client_id=client_data.client_id,
type_nat_foreign=client_data.type_nat_foreign,
name=client_data.name,
short_name=client_data.short_name,
rfc=client_data.rfc,
curp=client_data.curp,
client_or_provider=client_data.client_or_provider,
linking=client_data.linking,
transform_subassembly=client_data.transform_subassembly,
extra_information=client_data.extra_information,
web_key=client_data.web_key,
responsible=client_data.responsible,
position=client_data.position,
incoterm=client_data.incoterm,
is_national_provider=client_data.is_national_provider,
enabled_disabled=client_data.enabled_disabled
)
self.db.add(db_client)
self.db.flush() # Para obtener el ID antes del commit
# Crear dirección si se proporciona
if client_data.address:
db_address = GClientProviderAddress(
client_id=client_data.client_id,
**client_data.address.model_dump(exclude_unset=True)
)
self.db.add(db_address)
# Crear programas si se proporciona
if client_data.programs:
db_programs = GClientProviderPrograms(
client_id=client_data.client_id,
**client_data.programs.model_dump(exclude_unset=True)
)
self.db.add(db_programs)
self.db.commit()
self.db.refresh(db_client)
logger.info(f"Client/Provider created: {db_client.client_id} - {db_client.name}")
return self._get_client_with_relations(client_data.client_id)
except IntegrityError as e:
self.db.rollback()
logger.error(f"IntegrityError creating client/provider: {str(e)}")
raise HTTPException(status_code=400, detail="Client/Provider with this ID already exists")
except HTTPException:
raise
except Exception as e:
self.db.rollback()
logger.error(f"Error creating client/provider: {str(e)}")
raise HTTPException(status_code=500, detail="Error creating client/provider")
def get_client_provider(self, client_id: str) -> Optional[ClientProviderResponseDTO]:
"""
Obtiene un cliente/proveedor por ID
Args:
client_id: ID del cliente/proveedor
Returns:
ClientProviderResponseDTO o None si no existe
"""
return self._get_client_with_relations(client_id)
def _get_client_with_relations(self, client_id: str) -> Optional[ClientProviderResponseDTO]:
"""Método privado para obtener cliente con relaciones"""
client = self.db.query(ClientProvider).options(
joinedload(ClientProvider.address),
joinedload(ClientProvider.programs)
).filter(ClientProvider.client_id == client_id).first()
if not client:
return None
return ClientProviderResponseDTO.model_validate(client)
def list_clients_providers(
self,
skip: int = 0,
limit: int = 100,
search: Optional[str] = None,
client_or_provider: Optional[str] = None,
enabled_only: bool = False
) -> ClientProviderListDTO:
"""
Lista clientes/proveedores con filtros
Args:
skip: Número de registros a omitir
limit: Número máximo de registros a retornar
search: Texto de búsqueda (nombre, RFC, ID)
client_or_provider: Filtrar por tipo (C=Cliente, P=Proveedor)
enabled_only: Si True, solo retorna activos
Returns:
ClientProviderListDTO con la lista paginada
"""
query = self.db.query(ClientProvider)
# Aplicar filtros
if search:
search_pattern = f"%{search}%"
query = query.filter(
or_(
ClientProvider.name.ilike(search_pattern),
ClientProvider.short_name.ilike(search_pattern),
ClientProvider.rfc.ilike(search_pattern),
ClientProvider.client_id.ilike(search_pattern)
)
)
if client_or_provider:
query = query.filter(ClientProvider.client_or_provider == client_or_provider)
if enabled_only:
query = query.filter(ClientProvider.enabled_disabled == 1)
# Contar total
total = query.count()
# Aplicar paginación
clients = query.offset(skip).limit(limit).all()
# Convertir a DTOs básicos
client_dtos = [ClientProviderBasicDTO.model_validate(client) for client in clients]
return ClientProviderListDTO(
clients=client_dtos,
total=total,
page=(skip // limit) + 1 if limit > 0 else 1,
size=len(client_dtos)
)
def update_client_provider(self, client_id: str, client_data: ClientProviderUpdateDTO) -> Optional[ClientProviderResponseDTO]:
"""
Actualiza un cliente/proveedor
Args:
client_id: ID del cliente/proveedor a actualizar
client_data: Datos a actualizar
Returns:
ClientProviderResponseDTO actualizado o None si no existe
"""
client = self.db.query(ClientProvider).filter(ClientProvider.client_id == client_id).first()
if not client:
return None
try:
# Actualizar campos del cliente principal
update_data = client_data.model_dump(exclude_unset=True, exclude={'address', 'programs'})
for field, value in update_data.items():
setattr(client, field, value)
# Actualizar dirección
if client_data.address:
address = self.db.query(GClientProviderAddress).filter(GClientProviderAddress.client_id == client_id).first()
if address:
# Actualizar dirección existente
address_data = client_data.address.model_dump(exclude_unset=True)
for field, value in address_data.items():
setattr(address, field, value)
else:
# Crear nueva dirección
address = GClientProviderAddress(
client_id=client_id,
**client_data.address.model_dump(exclude_unset=True)
)
self.db.add(address)
# Actualizar programas
if client_data.programs:
programs = self.db.query(GClientProviderPrograms).filter(GClientProviderPrograms.client_id == client_id).first()
if programs:
# Actualizar programas existentes
programs_data = client_data.programs.model_dump(exclude_unset=True)
for field, value in programs_data.items():
setattr(programs, field, value)
else:
# Crear nuevos programas
programs = GClientProviderPrograms(
client_id=client_id,
**client_data.programs.model_dump(exclude_unset=True)
)
self.db.add(programs)
self.db.commit()
logger.info(f"Client/Provider updated: {client_id}")
return self._get_client_with_relations(client_id)
except Exception as e:
self.db.rollback()
logger.error(f"Error updating client/provider {client_id}: {str(e)}")
raise HTTPException(status_code=500, detail="Error updating client/provider")
def delete_client_provider(self, client_id: str) -> bool:
"""
Elimina un cliente/proveedor
Args:
client_id: ID del cliente/proveedor a eliminar
Returns:
True si se eliminó, False si no existe
"""
client = self.db.query(ClientProvider).filter(ClientProvider.client_id == client_id).first()
if not client:
return False
try:
self.db.delete(client) # Las relaciones se eliminan en cascada
self.db.commit()
logger.info(f"Client/Provider deleted: {client_id}")
return True
except Exception as e:
self.db.rollback()
logger.error(f"Error deleting client/provider {client_id}: {str(e)}")
raise HTTPException(status_code=500, detail="Error deleting client/provider")
def get_clients_only(self, skip: int = 0, limit: int = 100) -> List[ClientProviderBasicDTO]:
"""Obtiene solo clientes (C)"""
query = self.db.query(ClientProvider).filter(ClientProvider.client_or_provider == 'C')
clients = query.offset(skip).limit(limit).all()
return [ClientProviderBasicDTO.model_validate(client) for client in clients]
def get_providers_only(self, skip: int = 0, limit: int = 100) -> List[ClientProviderBasicDTO]:
"""Obtiene solo proveedores (P)"""
query = self.db.query(ClientProvider).filter(ClientProvider.client_or_provider == 'P')
providers = query.offset(skip).limit(limit).all()
return [ClientProviderBasicDTO.model_validate(provider) for provider in providers]
def search_by_rfc(self, rfc: str) -> List[ClientProviderBasicDTO]:
"""Busca clientes/proveedores por RFC"""
clients = self.db.query(ClientProvider).filter(ClientProvider.rfc.ilike(f"%{rfc}%")).all()
return [ClientProviderBasicDTO.model_validate(client) for client in clients]
def toggle_status(self, client_id: str) -> Optional[ClientProviderResponseDTO]:
"""Cambia el estado habilitado/deshabilitado"""
client = self.db.query(ClientProvider).filter(ClientProvider.client_id == client_id).first()
if not client:
return None
# Toggle status (1 = habilitado, 0 = deshabilitado)
client.enabled_disabled = 1 if client.enabled_disabled == 0 else 0
try:
self.db.commit()
logger.info(f"Client/Provider status toggled: {client_id} -> {client.enabled_disabled}")
return self._get_client_with_relations(client_id)
except Exception as e:
self.db.rollback()
logger.error(f"Error toggling status for {client_id}: {str(e)}")
raise HTTPException(status_code=500, detail="Error updating status")

View File

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

View File

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

View File

@@ -0,0 +1,69 @@
"""
Modelos ORM para gestión de empresa
"""
from sqlalchemy import Column, Integer, String, DateTime, Boolean, Text, SmallInteger
from sqlalchemy.sql import func
from core.database import Base
import enum
class GCompany(Base):
"""
Modelo para la tabla GCompany - Información de la empresa
"""
__tablename__ = "gcompany"
__table_args__ = {"schema": "a76"}
# Primary key
id = Column(String(3), primary_key=True, default='EMP', nullable=False)
# Control de registro único
consecutive = Column(Boolean, unique=True, default=True, nullable=False)
# Información básica de la empresa
name = Column(String(255), nullable=True)
rfc = Column(String(30), nullable=True)
main_activity = Column(String(255), nullable=True)
# Información del programa
program = Column(String(10), nullable=True)
program_number = Column(String(40), nullable=True)
prosec = Column(SmallInteger, nullable=True)
prosec_authorization = Column(String(20), nullable=True)
# Identificadores
manufacturer_id = Column(String(25), nullable=True)
broker_company = Column(String(10), nullable=True)
# Responsable
responsible = Column(String(80), nullable=True)
responsible_name = Column(String(20), nullable=True)
responsible_last_name = Column(String(20), nullable=True)
responsible_mother_last_name = Column(String(20), nullable=True)
responsible_rfc = Column(String(30), nullable=True)
position = Column(String(30), nullable=True)
# Configuración
logo = Column(String(255), nullable=True)
has_express_line = Column(Boolean, nullable=True)
order_format_type = Column(String(19), nullable=True)
previous_code = Column(SmallInteger, nullable=True)
is_service_company = Column(Boolean, nullable=True)
# Cliente y submaquila
client_name = Column(String(300), nullable=True)
subassembly_mode = Column(String(7), nullable=True)
# Información adicional
curp = Column(String(19), nullable=True)
inter_db_name = Column(String(100), nullable=True)
ctpat_svi = Column(String(100), nullable=True)
trusted_exporter_number = Column(String(50), nullable=True)
prevalidator_key = Column(String(20), nullable=True)
seventh_amendment = Column(Boolean, nullable=True) # FINALCONTADORAELECTRONICO renombrado
# Timestamps
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
updated_at = Column(DateTime(timezone=True), onupdate=func.now(), nullable=True)

View File

@@ -0,0 +1,176 @@
"""
Endpoints API para gestión de empresa
"""
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session
from typing import Optional
from core.database import get_core_db
from core.security import get_current_user, has_role
from .service import CompanyService
from .dto import CompanyCreateDTO, CompanyUpdateDTO, CompanyResponseDTO
router = APIRouter(prefix="/company")
@router.post("/", response_model=CompanyResponseDTO, status_code=status.HTTP_201_CREATED)
async def create_company(
company_data: CompanyCreateDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Create a new company in the system
Only one company can exist per system due to the unique consecutive field.
"""
service = CompanyService(db)
return service.create_company(company_data)
@router.get("/", response_model=Optional[CompanyResponseDTO])
async def get_company(
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Get the registered company information
Returns the unique company in the system or None if it doesn't exist.
"""
service = CompanyService(db)
company = service.get_company()
if not company:
raise HTTPException(status_code=404, detail="No company found")
return company
@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")
@router.get("/status/exists", response_model=dict)
async def check_company_exists(
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Check if a company is registered in the system
"""
service = CompanyService(db)
exists = service.exists_company()
return {"exists": exists, "message": "Company found" if exists else "No company registered"}
# Specific endpoints for important fields
@router.get("/info/basic", response_model=dict)
async def get_company_basic_info(
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Get basic company information (name, RFC, main activity)
"""
service = CompanyService(db)
company = service.get_company()
if not company:
raise HTTPException(status_code=404, detail="No company found")
return {
"name": company.name,
"rfc": company.rfc,
"main_activity": company.main_activity,
"logo": company.logo
}
@router.get("/info/responsible", response_model=dict)
async def get_company_responsible_info(
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Get company responsible person information
"""
service = CompanyService(db)
company = service.get_company()
if not company:
raise HTTPException(status_code=404, detail="No company found")
return {
"responsible": company.responsible,
"responsible_name": company.responsible_name,
"responsible_last_name": company.responsible_last_name,
"responsible_mother_last_name": company.responsible_mother_last_name,
"responsible_rfc": company.responsible_rfc,
"position": company.position
}
@router.get("/info/program", response_model=dict)
async def get_company_program_info(
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Get company program information
"""
service = CompanyService(db)
company = service.get_company()
if not company:
raise HTTPException(status_code=404, detail="No company found")
return {
"program": company.program,
"program_number": company.program_number,
"prosec": company.prosec,
"prosec_authorization": company.prosec_authorization,
"manufacturer_id": company.manufacturer_id
}

View File

@@ -0,0 +1,184 @@
"""
Capa de servicio para lógica de negocio de empresa
"""
from sqlalchemy.orm import Session
from sqlalchemy.exc import IntegrityError
from fastapi import HTTPException
from typing import List, Optional
import logging
from .models import GCompany
from .dto import CompanyCreateDTO, CompanyUpdateDTO, CompanyResponseDTO
logger = logging.getLogger(__name__)
class CompanyService:
"""Servicio para gestión de empresa"""
def __init__(self, db: Session):
self.db = db
def create_company(self, company_data: CompanyCreateDTO) -> CompanyResponseDTO:
"""
Crea una nueva empresa en el sistema
Args:
company_data: Datos de la empresa a crear
Returns:
CompanyResponseDTO con información de la empresa creada
Raises:
HTTPException: Si ya existe una empresa o error en la creación
"""
try:
# Verificar que no exista ya una empresa (solo puede haber una por el consecutivo único)
existing = self.db.query(GCompany).filter(GCompany.consecutive == True).first()
if existing:
raise HTTPException(status_code=400, detail="A company is already registered in the system")
# Crear empresa
db_company = GCompany(
id=company_data.id,
consecutive=company_data.consecutive,
name=company_data.name,
rfc=company_data.rfc,
main_activity=company_data.main_activity,
program=company_data.program,
program_number=company_data.program_number,
prosec=company_data.prosec,
prosec_authorization=company_data.prosec_authorization,
manufacturer_id=company_data.manufacturer_id,
broker_company=company_data.broker_company,
responsible=company_data.responsible,
responsible_name=company_data.responsible_name,
responsible_last_name=company_data.responsible_last_name,
responsible_mother_last_name=company_data.responsible_mother_last_name,
responsible_rfc=company_data.responsible_rfc,
position=company_data.position,
logo=company_data.logo,
has_express_line=company_data.has_express_line,
order_format_type=company_data.order_format_type,
previous_code=company_data.previous_code,
is_service_company=company_data.is_service_company,
client_name=company_data.client_name,
subassembly_mode=company_data.subassembly_mode,
curp=company_data.curp,
inter_db_name=company_data.inter_db_name,
ctpat_svi=company_data.ctpat_svi,
trusted_exporter_number=company_data.trusted_exporter_number,
prevalidator_key=company_data.prevalidator_key,
seventh_amendment=company_data.seventh_amendment
)
self.db.add(db_company)
self.db.commit()
self.db.refresh(db_company)
logger.info(f"Company created: {db_company.id} - {db_company.name}")
return CompanyResponseDTO.model_validate(db_company)
except IntegrityError as e:
self.db.rollback()
logger.error(f"IntegrityError creating company: {str(e)}")
raise HTTPException(status_code=400, detail="Integrity error: A company already exists in the system")
except HTTPException:
raise
except Exception as e:
self.db.rollback()
logger.error(f"Error creating company: {str(e)}")
raise HTTPException(status_code=500, detail="Error creating company")
def get_company(self) -> Optional[CompanyResponseDTO]:
"""
Obtiene la empresa (solo puede haber una)
Returns:
CompanyResponseDTO o None si no existe
"""
company = self.db.query(GCompany).filter(GCompany.consecutive == True).first()
if not company:
return None
return CompanyResponseDTO.model_validate(company)
def get_company_by_id(self, company_id: str) -> Optional[CompanyResponseDTO]:
"""
Obtiene una empresa por ID
Args:
company_id: ID de la empresa
Returns:
CompanyResponseDTO o None si no existe
"""
company = self.db.query(GCompany).filter(GCompany.id == company_id).first()
if not company:
return None
return CompanyResponseDTO.model_validate(company)
def update_company(self, company_id: str, company_data: CompanyUpdateDTO) -> Optional[CompanyResponseDTO]:
"""
Actualiza una empresa
Args:
company_id: ID de la empresa a actualizar
company_data: Datos a actualizar
Returns:
CompanyResponseDTO actualizada o None si no existe
"""
company = self.db.query(GCompany).filter(GCompany.id == company_id).first()
if not company:
return None
# Actualizar solo campos proporcionados
update_data = company_data.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(company, field, value)
try:
self.db.commit()
self.db.refresh(company)
logger.info(f"Company updated: {company_id}")
return CompanyResponseDTO.model_validate(company)
except Exception as e:
self.db.rollback()
logger.error(f"Error updating company {company_id}: {str(e)}")
raise HTTPException(status_code=500, detail="Error updating company")
def delete_company(self, company_id: str) -> bool:
"""
Elimina una empresa
Args:
company_id: ID de la empresa a eliminar
Returns:
True si se eliminó, False si no existe
"""
company = self.db.query(GCompany).filter(GCompany.id == company_id).first()
if not company:
return False
try:
self.db.delete(company)
self.db.commit()
logger.info(f"Company deleted: {company_id}")
return True
except Exception as e:
self.db.rollback()
logger.error(f"Error deleting company {company_id}: {str(e)}")
raise HTTPException(status_code=500, detail="Error deleting company")
def exists_company(self) -> bool:
"""
Verifica si existe una empresa registrada
Returns:
True si existe una empresa, False en caso contrario
"""
return self.db.query(GCompany).filter(GCompany.consecutive == True).first() is not None

View File

@@ -15,7 +15,7 @@ from .dto import (
)
from .service import LicenseService
router = APIRouter(prefix="/licenses", tags=["Licenses"])
router = APIRouter(prefix="/licenses")
@router.post("/", response_model=LicenseResponseDTO, status_code=201)

View File

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

View File

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

View File

@@ -0,0 +1,88 @@
"""
Modelos ORM para gestión de partes/componentes
"""
from sqlalchemy import Column, Integer, String, DateTime, Boolean, Text, Numeric, SmallInteger, ForeignKey
from sqlalchemy.sql import func
from sqlalchemy.orm import relationship
from core.database import Base
import enum
# Importar modelos relacionados para type hints y relationships
from typing import TYPE_CHECKING, Optional
if TYPE_CHECKING:
from api.v1.modules.public.reference_data.countries.models import Country
from api.v1.modules.public.reference_data.currency_types.models import CurrencyType
from api.v1.modules.a76.classes.models import Class
class Part(Base):
"""
Modelo para la tabla GPartes - Información de partes en los sistemas SCAII (N), SCAF (S) Y WINSAAI (W)
"""
__tablename__ = "parts"
__table_args__ = {"schema": "a76"}
# Primary key compuesta
client_key = Column(Integer, primary_key=True, nullable=False)
part_number = Column(String(49), primary_key=True, nullable=False)
# Basic information
fraction = Column(String(10), nullable=True)
description_spanish = Column(String(500), nullable=True)
description_english = Column(String(500), nullable=True)
part_class = Column(String(8), nullable=True)
unit_of_measure = Column(String(5), nullable=True)
commercial_part_number = Column(String(70), nullable=True)
country_of_origin = Column(String(3), ForeignKey('public.countries.m3_key'), nullable=True)
# Pricing and currency
unit_cost = Column(Numeric(23, 8), nullable=True)
currency_type = Column(String(2), nullable=True)
currency_key = Column(String(3), ForeignKey('public.currency_types.code'), nullable=True)
# Weight information
unit_weight = Column(Numeric(19, 8), nullable=True)
weight_type = Column(String(6), nullable=True)
# Classification and regulatory
us_fraction = Column(String(16), nullable=True) # FRACCIONAME
fda_key = Column(String(20), nullable=True)
fcc_key = Column(String(30), nullable=True)
license_code = Column(String(3), nullable=True)
eccn = Column(String(20), nullable=True) # Export Control Classification Number
export_code = Column(String(2), nullable=True)
exclusion_symbol = Column(String(19), nullable=True) # SIMBOLOEXCLIC
# Additional information
supplier = Column(String(14), nullable=True)
alternate_unit_measure = Column(String(14), nullable=True)
added_value = Column(Numeric(23, 8), nullable=True)
# Status and dates
enabled_disabled = Column(SmallInteger, nullable=True)
creation_date = Column(Integer, nullable=True) # FECHACREACIONPARTE
modification_date = Column(Integer, nullable=True) # FECHAMODIFICA
modification_date_iso = Column(DateTime(timezone=True), nullable=True) # FECHAMODIFICA_ISO
# Media
part_photo = Column(String(255), nullable=True)
# Relationships
country = relationship("Country", foreign_keys=[country_of_origin])
currency = relationship("CurrencyType", foreign_keys=[currency_key])
# Relationship with Class through composite foreign key
# Note: This requires both client_key and part_class to match client_key and class_code in Class
part_class_info = relationship(
"Class",
primaryjoin="and_(Part.client_key == Class.client_key, Part.part_class == Class.class_code)",
foreign_keys="[Part.client_key, Part.part_class]",
viewonly=True,
back_populates="parts"
)
def __repr__(self):
return f"<Part(client_key={self.client_key}, part_number='{self.part_number}', description='{self.description_spanish}')>"

View File

@@ -0,0 +1,273 @@
"""
Endpoints API para gestión de partes/componentes
"""
from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy.orm import Session
from typing import List, Optional
from core.database import get_core_db
from core.security import get_current_user, has_role
from .service import PartService
from .dto import (
PartCreateDTO,
PartUpdateDTO,
PartResponseDTO,
PartBasicDTO,
PartListDTO,
PartSearchDTO
)
router = APIRouter(prefix="/parts")
@router.post("/", response_model=PartResponseDTO, status_code=status.HTTP_201_CREATED)
async def create_part(
part_data: PartCreateDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Create a new part in the system
"""
service = PartService(db)
return service.create_part(part_data)
@router.get("/", response_model=PartListDTO)
async def list_parts(
skip: int = Query(0, ge=0, description="Number of records to skip"),
limit: int = Query(100, ge=1, le=1000, description="Maximum number of records to return"),
client_key: Optional[int] = Query(None, description="Filter by client key"),
part_number: Optional[str] = Query(None, description="Search by part number"),
description: Optional[str] = Query(None, description="Search in descriptions"),
fraction: Optional[str] = Query(None, description="Filter by tariff fraction"),
supplier: Optional[str] = Query(None, description="Filter by supplier"),
enabled_only: bool = Query(False, description="Show only enabled parts"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
List parts with optional filters and pagination
"""
service = PartService(db)
search_params = PartSearchDTO(
client_key=client_key,
part_number=part_number,
description=description,
fraction=fraction,
supplier=supplier,
enabled_only=enabled_only
)
return service.list_parts(skip, limit, search_params)
@router.get("/client/{client_key}", response_model=List[PartBasicDTO])
async def get_parts_by_client(
client_key: int,
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Get all parts for a specific client
"""
service = PartService(db)
return service.search_by_client(client_key, skip, limit)
@router.get("/search/fraction/{fraction}", response_model=List[PartBasicDTO])
async def search_by_fraction(
fraction: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Search parts by tariff fraction
"""
service = PartService(db)
return service.search_by_fraction(fraction)
@router.get("/search/supplier/{supplier}", response_model=List[PartBasicDTO])
async def search_by_supplier(
supplier: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Search parts by supplier
"""
service = PartService(db)
return service.search_by_supplier(supplier)
@router.get("/search/country/{country_code}", response_model=List[PartBasicDTO])
async def get_parts_by_country(
country_code: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Get parts by country of origin
"""
service = PartService(db)
return service.get_parts_by_country(country_code)
@router.get("/statistics", response_model=dict)
async def get_parts_statistics(
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Get basic parts statistics
"""
service = PartService(db)
return service.get_parts_statistics()
@router.get("/{client_key}/{part_number}", response_model=PartResponseDTO)
async def get_part(
client_key: int,
part_number: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Get part by composite key (client_key + part_number)
"""
service = PartService(db)
part = service.get_part(client_key, part_number)
if not part:
raise HTTPException(
status_code=404,
detail=f"Part with client_key '{client_key}' and part_number '{part_number}' not found"
)
return part
@router.put("/{client_key}/{part_number}", response_model=PartResponseDTO)
async def update_part(
client_key: int,
part_number: str,
part_data: PartUpdateDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Update part information
"""
service = PartService(db)
part = service.update_part(client_key, part_number, part_data)
if not part:
raise HTTPException(
status_code=404,
detail=f"Part with client_key '{client_key}' and part_number '{part_number}' not found"
)
return part
@router.delete("/{client_key}/{part_number}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_part(
client_key: int,
part_number: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Delete part from the system
Note: This will completely remove the part from the system.
"""
service = PartService(db)
if not service.delete_part(client_key, part_number):
raise HTTPException(
status_code=404,
detail=f"Part with client_key '{client_key}' and part_number '{part_number}' not found"
)
@router.patch("/{client_key}/{part_number}/toggle-status", response_model=PartResponseDTO)
async def toggle_part_status(
client_key: int,
part_number: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Toggle part enabled/disabled status
"""
service = PartService(db)
part = service.toggle_status(client_key, part_number)
if not part:
raise HTTPException(
status_code=404,
detail=f"Part with client_key '{client_key}' and part_number '{part_number}' not found"
)
return part
# Endpoints específicos para información detallada
@router.get("/{client_key}/{part_number}/basic", response_model=PartBasicDTO)
async def get_part_basic_info(
client_key: int,
part_number: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Get basic information for a part
"""
service = PartService(db)
part = service.get_part(client_key, part_number)
if not part:
raise HTTPException(
status_code=404,
detail=f"Part with client_key '{client_key}' and part_number '{part_number}' not found"
)
return PartBasicDTO(
client_key=part.client_key,
part_number=part.part_number,
description_spanish=part.description_spanish,
description_english=part.description_english,
part_class=part.part_class,
unit_cost=part.unit_cost,
currency_key=part.currency_key,
enabled_disabled=part.enabled_disabled
)
@router.get("/{client_key}/{part_number}/regulatory", response_model=dict)
async def get_part_regulatory_info(
client_key: int,
part_number: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Get regulatory information for a part (FDA, FCC, ECCN, etc.)
"""
service = PartService(db)
part = service.get_part(client_key, part_number)
if not part:
raise HTTPException(
status_code=404,
detail=f"Part with client_key '{client_key}' and part_number '{part_number}' not found"
)
return {
"client_key": part.client_key,
"part_number": part.part_number,
"fraction": part.fraction,
"us_fraction": part.us_fraction,
"fda_key": part.fda_key,
"fcc_key": part.fcc_key,
"license_code": part.license_code,
"eccn": part.eccn,
"export_code": part.export_code,
"exclusion_symbol": part.exclusion_symbol
}

View File

@@ -0,0 +1,275 @@
"""
Capa de servicio para lógica de negocio de partes/componentes
"""
from sqlalchemy.orm import Session
from sqlalchemy.exc import IntegrityError
from sqlalchemy import or_, and_, func
from fastapi import HTTPException
from typing import List, Optional
import logging
from datetime import datetime
from .models import Part
from .dto import PartCreateDTO, PartUpdateDTO
logger = logging.getLogger(__name__)
class PartService:
"""
Servicio para gestión de partes/componentes
"""
@staticmethod
def create_part(db: Session, part_data: PartCreateDTO) -> Part:
"""
Crear una nueva parte
"""
try:
db_part = Part(**part_data.model_dump())
db.add(db_part)
db.commit()
db.refresh(db_part)
return db_part
except IntegrityError as e:
db.rollback()
logger.error(f"Error creating part: {e}")
raise HTTPException(status_code=400, detail="Part with this client_key and part_number already exists")
except Exception as e:
db.rollback()
logger.error(f"Unexpected error creating part: {e}")
raise HTTPException(status_code=500, detail="Error creating part")
@staticmethod
def get_part(db: Session, client_key: int, part_number: str) -> Optional[Part]:
"""
Obtener una parte por clave de cliente y número de parte
"""
try:
return db.query(Part).filter(
and_(
Part.client_key == client_key,
Part.part_number == part_number
)
).first()
except Exception as e:
logger.error(f"Error getting part: {e}")
raise HTTPException(status_code=500, detail="Error retrieving part")
@staticmethod
def get_parts_paginated(
db: Session,
skip: int = 0,
limit: int = 100,
search: Optional[str] = None,
client_key: Optional[int] = None,
fraction: Optional[str] = None,
country_of_origin: Optional[str] = None
) -> tuple[List[Part], int]:
"""
Obtener partes con paginación y filtros
"""
try:
query = db.query(Part)
# Aplicar filtros
if search:
query = query.filter(or_(
Part.description_spanish.ilike(f"%{search}%"),
Part.description_english.ilike(f"%{search}%"),
Part.part_number.ilike(f"%{search}%")
))
if client_key is not None:
query = query.filter(Part.client_key == client_key)
if fraction:
query = query.filter(Part.fraction == fraction)
if country_of_origin:
query = query.filter(Part.country_of_origin == country_of_origin)
# Contar total
total = query.count()
# Aplicar paginación
parts = query.offset(skip).limit(limit).all()
return parts, total
except Exception as e:
logger.error(f"Error getting paginated parts: {e}")
raise HTTPException(status_code=500, detail="Error retrieving parts")
@staticmethod
def get_parts_by_client(db: Session, client_key: int) -> List[Part]:
"""
Obtener todas las partes de un cliente específico
"""
try:
return db.query(Part).filter(Part.client_key == client_key).all()
except Exception as e:
logger.error(f"Error getting parts by client: {e}")
raise HTTPException(status_code=500, detail="Error retrieving client parts")
@staticmethod
def search_parts_by_fraction(db: Session, fraction: str) -> List[Part]:
"""
Buscar partes por fracción arancelaria
"""
try:
return db.query(Part).filter(
or_(
Part.fraction.ilike(f"%{fraction}%"),
Part.us_fraction.ilike(f"%{fraction}%")
)
).all()
except Exception as e:
logger.error(f"Error searching parts by fraction: {e}")
raise HTTPException(status_code=500, detail="Error searching parts by fraction")
@staticmethod
def search_parts_by_supplier(db: Session, supplier: str) -> List[Part]:
"""
Buscar partes por proveedor
"""
try:
return db.query(Part).filter(Part.supplier.ilike(f"%{supplier}%")).all()
except Exception as e:
logger.error(f"Error searching parts by supplier: {e}")
raise HTTPException(status_code=500, detail="Error searching parts by supplier")
@staticmethod
def search_parts_by_country(db: Session, country_code: str) -> List[Part]:
"""
Buscar partes por país de origen
"""
try:
return db.query(Part).filter(Part.country_of_origin == country_code).all()
except Exception as e:
logger.error(f"Error searching parts by country: {e}")
raise HTTPException(status_code=500, detail="Error searching parts by country")
@staticmethod
def update_part(db: Session, client_key: int, part_number: str, part_data: PartUpdateDTO) -> Optional[Part]:
"""
Actualizar una parte existente
"""
try:
db_part = PartService.get_part(db, client_key, part_number)
if not db_part:
return None
# Actualizar campos
for field, value in part_data.model_dump(exclude_unset=True).items():
setattr(db_part, field, value)
db.commit()
db.refresh(db_part)
return db_part
except Exception as e:
db.rollback()
logger.error(f"Error updating part: {e}")
raise HTTPException(status_code=500, detail="Error updating part")
@staticmethod
def delete_part(db: Session, client_key: int, part_number: str) -> bool:
"""
Eliminar una parte
"""
try:
db_part = PartService.get_part(db, client_key, part_number)
if not db_part:
return False
db.delete(db_part)
db.commit()
return True
except Exception as e:
db.rollback()
logger.error(f"Error deleting part: {e}")
raise HTTPException(status_code=500, detail="Error deleting part")
@staticmethod
def toggle_part_status(db: Session, client_key: int, part_number: str) -> Optional[Part]:
"""
Cambiar el estado habilitado/deshabilitado de una parte
"""
try:
db_part = PartService.get_part(db, client_key, part_number)
if not db_part:
return None
# Toggle status (assuming 1 = enabled, 0 = disabled)
db_part.enabled_disabled = 1 if db_part.enabled_disabled == 0 else 0
db.commit()
db.refresh(db_part)
return db_part
except Exception as e:
db.rollback()
logger.error(f"Error toggling part status: {e}")
raise HTTPException(status_code=500, detail="Error toggling part status")
@staticmethod
def get_parts_statistics(db: Session) -> dict:
"""
Obtener estadísticas de partes
"""
try:
total_parts = db.query(Part).count()
# Partes por cliente
parts_by_client = db.query(
Part.client_key,
func.count(Part.part_number).label('count')
).group_by(Part.client_key).all()
# Partes por país de origen
parts_by_country = db.query(
Part.country_of_origin,
func.count(Part.part_number).label('count')
).filter(Part.country_of_origin.isnot(None))\
.group_by(Part.country_of_origin).all()
# Partes habilitadas vs deshabilitadas
enabled_parts = db.query(Part).filter(Part.enabled_disabled == 1).count()
disabled_parts = db.query(Part).filter(Part.enabled_disabled == 0).count()
return {
"total_parts": total_parts,
"enabled_parts": enabled_parts,
"disabled_parts": disabled_parts,
"parts_by_client": [{"client_key": item[0], "count": item[1]} for item in parts_by_client],
"parts_by_country": [{"country": item[0], "count": item[1]} for item in parts_by_country]
}
except Exception as e:
logger.error(f"Error getting parts statistics: {e}")
raise HTTPException(status_code=500, detail="Error retrieving parts statistics")
@staticmethod
def get_part_regulatory_info(db: Session, client_key: int, part_number: str) -> Optional[dict]:
"""
Obtener información regulatoria específica de una parte
"""
try:
db_part = PartService.get_part(db, client_key, part_number)
if not db_part:
return None
return {
"client_key": db_part.client_key,
"part_number": db_part.part_number,
"fraction": db_part.fraction,
"us_fraction": db_part.us_fraction,
"fda_key": db_part.fda_key,
"fcc_key": db_part.fcc_key,
"license_code": db_part.license_code,
"eccn": db_part.eccn,
"export_code": db_part.export_code,
"exclusion_symbol": db_part.exclusion_symbol,
"country_of_origin": db_part.country_of_origin
}
except Exception as e:
logger.error(f"Error getting part regulatory info: {e}")
raise HTTPException(status_code=500, detail="Error retrieving part regulatory information")

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,38 @@
from pydantic import BaseModel, Field, ConfigDict
from typing import Optional
from datetime import datetime
class PedimentoConfigSurchargesBase(BaseModel):
"""Base schema for Pedimento Config Surcharges"""
pedimento_id: int = Field(..., description="Pedimento ID")
tenant_id: int = Field(..., description="Tenant ID")
surcharge_igi: Optional[int] = Field(None, description="Surcharge IGI")
surcharge_dta: Optional[int] = Field(None, description="Surcharge DTA")
surcharge_vat: Optional[int] = Field(None, description="Surcharge VAT")
surcharge_isan: Optional[int] = Field(None, description="Surcharge ISAN")
surcharge_ieps: Optional[int] = Field(None, description="Surcharge IEPS")
surcharge_cc: Optional[int] = Field(None, description="Surcharge CC")
class PedimentoConfigSurchargesCreate(PedimentoConfigSurchargesBase):
"""Schema for creating a new Pedimento Config Surcharges"""
pass
class PedimentoConfigSurchargesUpdate(BaseModel):
"""Schema for updating a Pedimento Config Surcharges"""
surcharge_igi: Optional[int] = None
surcharge_dta: Optional[int] = None
surcharge_vat: Optional[int] = None
surcharge_isan: Optional[int] = None
surcharge_ieps: Optional[int] = None
surcharge_cc: Optional[int] = None
class PedimentoConfigSurchargesResponse(PedimentoConfigSurchargesBase):
"""Schema for Pedimento Config Surcharges response"""
id: int
created_at: datetime
model_config = ConfigDict(from_attributes=True)

View File

@@ -0,0 +1,36 @@
from pydantic import BaseModel, Field, ConfigDict
from typing import Optional
from datetime import datetime
class PedimentoConfigUpdateRectificationBase(BaseModel):
"""Base schema for Pedimento Config Update Rectification"""
pedimento_id: int = Field(..., description="Pedimento ID")
tenant_id: int = Field(..., description="Tenant ID")
update_vat: Optional[int] = Field(None, description="Update VAT")
update_advalorem: Optional[int] = Field(None, description="Update advalorem")
update_cc: Optional[int] = Field(None, description="Update CC")
update_ieps: Optional[int] = Field(None, description="Update IEPS")
calculate_surcharge: Optional[int] = Field(None, description="Calculate surcharge")
class PedimentoConfigUpdateRectificationCreate(PedimentoConfigUpdateRectificationBase):
"""Schema for creating a new Pedimento Config Update Rectification"""
pass
class PedimentoConfigUpdateRectificationUpdate(BaseModel):
"""Schema for updating a Pedimento Config Update Rectification"""
update_vat: Optional[int] = None
update_advalorem: Optional[int] = None
update_cc: Optional[int] = None
update_ieps: Optional[int] = None
calculate_surcharge: Optional[int] = None
class PedimentoConfigUpdateRectificationResponse(PedimentoConfigUpdateRectificationBase):
"""Schema for Pedimento Config Update Rectification response"""
id: int
created_at: datetime
model_config = ConfigDict(from_attributes=True)

View File

@@ -0,0 +1,34 @@
from pydantic import BaseModel, Field, ConfigDict
from typing import Optional
from datetime import datetime
class PedimentoConfigUpdatesBase(BaseModel):
"""Base schema for Pedimento Config Updates"""
pedimento_id: int = Field(..., description="Pedimento ID")
tenant_id: int = Field(..., description="Tenant ID")
update_vat: Optional[int] = Field(None, description="Update VAT")
update_advalorem: Optional[int] = Field(None, description="Update advalorem")
update_cc: Optional[int] = Field(None, description="Update CC")
update_ieps: Optional[int] = Field(None, description="Update IEPS")
class PedimentoConfigUpdatesCreate(PedimentoConfigUpdatesBase):
"""Schema for creating a new Pedimento Config Updates"""
pass
class PedimentoConfigUpdatesUpdate(BaseModel):
"""Schema for updating a Pedimento Config Updates"""
update_vat: Optional[int] = None
update_advalorem: Optional[int] = None
update_cc: Optional[int] = None
update_ieps: Optional[int] = None
class PedimentoConfigUpdatesResponse(PedimentoConfigUpdatesBase):
"""Schema for Pedimento Config Updates response"""
id: int
created_at: datetime
model_config = ConfigDict(from_attributes=True)

View File

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

View File

@@ -0,0 +1,50 @@
from pydantic import BaseModel, Field, ConfigDict
from typing import Optional
from datetime import datetime, time
class PedimentoDatesBase(BaseModel):
"""Base schema for Pedimento Dates"""
pedimento_id: int = Field(..., description="Pedimento ID")
tenant_id: int = Field(..., description="Tenant ID")
entry_date: Optional[datetime] = Field(None, description="Entry date")
pedimento_date: Optional[datetime] = Field(None, description="Pedimento date")
payment_date: Optional[datetime] = Field(None, description="Payment date")
rectification_payment_date: Optional[datetime] = Field(None, description="Rectification payment date")
extraction_date: Optional[datetime] = Field(None, description="Extraction date")
submission_date: Optional[datetime] = Field(None, description="Submission date")
eucan_date: Optional[datetime] = Field(None, description="EUCAN date")
original_date: Optional[datetime] = Field(None, description="Original date")
start_date: Optional[datetime] = Field(None, description="Start date")
end_date: Optional[datetime] = Field(None, description="End date")
capture_date: Optional[datetime] = Field(None, description="Capture date")
capture_time: Optional[time] = Field(None, description="Capture time")
class PedimentoDatesCreate(PedimentoDatesBase):
"""Schema for creating a new Pedimento Dates"""
pass
class PedimentoDatesUpdate(BaseModel):
"""Schema for updating a Pedimento Dates"""
entry_date: Optional[datetime] = None
pedimento_date: Optional[datetime] = None
payment_date: Optional[datetime] = None
rectification_payment_date: Optional[datetime] = None
extraction_date: Optional[datetime] = None
submission_date: Optional[datetime] = None
eucan_date: Optional[datetime] = None
original_date: Optional[datetime] = None
start_date: Optional[datetime] = None
end_date: Optional[datetime] = None
capture_date: Optional[datetime] = None
capture_time: Optional[time] = None
class PedimentoDatesResponse(PedimentoDatesBase):
"""Schema for Pedimento Dates response"""
id: int
created_at: datetime
model_config = ConfigDict(from_attributes=True)

View File

@@ -0,0 +1,45 @@
from pydantic import BaseModel, Field, ConfigDict
from typing import Optional
from decimal import Decimal
from datetime import datetime
class PedimentoDecrementablesBase(BaseModel):
"""Base schema for Pedimento Decrementables"""
pedimento_id: int = Field(..., description="Pedimento ID")
tenant_id: int = Field(..., description="Tenant ID")
freight: Optional[Decimal] = Field(None, description="Freight")
insurance: Optional[Decimal] = Field(None, description="Insurance")
loading: Optional[Decimal] = Field(None, description="Loading")
unloading: Optional[Decimal] = Field(None, description="Unloading")
others: Optional[Decimal] = Field(None, description="Others")
currency: Optional[str] = Field(None, max_length=3, description="Currency")
currency_factor: Optional[Decimal] = Field(None, description="Currency factor")
not_affect_usd_value: Optional[int] = Field(None, description="Not affect USD value")
not_affect_customs_value: Optional[int] = Field(None, description="Not affect customs value")
class PedimentoDecrementablesCreate(PedimentoDecrementablesBase):
"""Schema for creating a new Pedimento Decrementables"""
pass
class PedimentoDecrementablesUpdate(BaseModel):
"""Schema for updating a Pedimento Decrementables"""
freight: Optional[Decimal] = None
insurance: Optional[Decimal] = None
loading: Optional[Decimal] = None
unloading: Optional[Decimal] = None
others: Optional[Decimal] = None
currency: Optional[str] = Field(None, max_length=3)
currency_factor: Optional[Decimal] = None
not_affect_usd_value: Optional[int] = None
not_affect_customs_value: Optional[int] = None
class PedimentoDecrementablesResponse(PedimentoDecrementablesBase):
"""Schema for Pedimento Decrementables response"""
id: int
created_at: datetime
model_config = ConfigDict(from_attributes=True)

View File

@@ -0,0 +1,47 @@
from pydantic import BaseModel, Field, ConfigDict
from typing import Optional
from decimal import Decimal
from datetime import datetime
class PedimentoIncrementablesBase(BaseModel):
"""Base schema for Pedimento Incrementables"""
pedimento_id: int = Field(..., description="Pedimento ID")
tenant_id: int = Field(..., description="Tenant ID")
insured_value: Optional[Decimal] = Field(None, description="Insured value")
freight: Optional[Decimal] = Field(None, description="Freight")
insurance: Optional[Decimal] = Field(None, description="Insurance")
packaging: Optional[Decimal] = Field(None, description="Packaging")
others: Optional[Decimal] = Field(None, description="Others")
deductibles: Optional[Decimal] = Field(None, description="Deductibles")
currency: Optional[str] = Field(None, max_length=3, description="Currency")
currency_factor: Optional[Decimal] = Field(None, description="Currency factor")
not_affect_usd_value: Optional[int] = Field(None, description="Not affect USD value")
not_affect_customs_value: Optional[int] = Field(None, description="Not affect customs value")
class PedimentoIncrementablesCreate(PedimentoIncrementablesBase):
"""Schema for creating a new Pedimento Incrementables"""
pass
class PedimentoIncrementablesUpdate(BaseModel):
"""Schema for updating a Pedimento Incrementables"""
insured_value: Optional[Decimal] = None
freight: Optional[Decimal] = None
insurance: Optional[Decimal] = None
packaging: Optional[Decimal] = None
others: Optional[Decimal] = None
deductibles: Optional[Decimal] = None
currency: Optional[str] = Field(None, max_length=3)
currency_factor: Optional[Decimal] = None
not_affect_usd_value: Optional[int] = None
not_affect_customs_value: Optional[int] = None
class PedimentoIncrementablesResponse(PedimentoIncrementablesBase):
"""Schema for Pedimento Incrementables response"""
id: int
created_at: datetime
model_config = ConfigDict(from_attributes=True)

View File

@@ -0,0 +1,33 @@
from pydantic import BaseModel, Field, ConfigDict
from typing import Optional
from decimal import Decimal
from datetime import datetime
class PedimentoIndexesBase(BaseModel):
"""Base schema for Pedimento Indexes"""
pedimento_id: int = Field(..., description="Pedimento ID")
tenant_id: int = Field(..., description="Tenant ID")
update_factor_type: Optional[int] = Field(None, description="Update factor type")
update_factor: Optional[Decimal] = Field(None, description="Update factor")
manual_update_factor: Optional[int] = Field(None, description="Manual update factor")
class PedimentoIndexesCreate(PedimentoIndexesBase):
"""Schema for creating a new Pedimento Indexes"""
pass
class PedimentoIndexesUpdate(BaseModel):
"""Schema for updating a Pedimento Indexes"""
update_factor_type: Optional[int] = None
update_factor: Optional[Decimal] = None
manual_update_factor: Optional[int] = None
class PedimentoIndexesResponse(PedimentoIndexesBase):
"""Schema for Pedimento Indexes response"""
id: int
created_at: datetime
model_config = ConfigDict(from_attributes=True)

View File

@@ -0,0 +1,50 @@
from pydantic import BaseModel, Field, ConfigDict
from typing import Optional
from datetime import datetime, date as Date, time as Time
class PedimentoPaymentsBase(BaseModel):
"""Base schema for Pedimento Payments"""
pedimento_id: int = Field(..., description="Pedimento ID")
tenant_id: int = Field(..., description="Tenant ID")
acknowledgment: Optional[str] = Field(None, max_length=20, description="Acknowledgment")
operation_number: Optional[str] = Field(None, max_length=14, description="Operation number")
bank_code: Optional[int] = Field(None, description="Bank code")
cashier: Optional[str] = Field(None, max_length=2, description="Cashier")
date: Optional[Date] = Field(None, description="Date")
time: Optional[Time] = Field(None, description="Time")
shift: Optional[str] = Field(None, max_length=1, description="Shift")
total_cash_paid: Optional[int] = Field(None, description="Total cash paid")
total_contributions: Optional[int] = Field(None, description="Total contributions")
counter_payment: Optional[int] = Field(None, description="Counter payment")
pece_code: Optional[str] = Field(None, max_length=5, description="PECE code")
payment_id: Optional[int] = Field(None, description="Payment ID")
class PedimentoPaymentsCreate(PedimentoPaymentsBase):
"""Schema for creating a new Pedimento Payments"""
pass
class PedimentoPaymentsUpdate(BaseModel):
"""Schema for updating a Pedimento Payments"""
acknowledgment: Optional[str] = Field(None, max_length=20)
operation_number: Optional[str] = Field(None, max_length=14)
bank_code: Optional[int] = None
cashier: Optional[str] = Field(None, max_length=2)
date: Optional[Date] = None
time: Optional[Time] = None
shift: Optional[str] = Field(None, max_length=1)
total_cash_paid: Optional[int] = None
total_contributions: Optional[int] = None
counter_payment: Optional[int] = None
pece_code: Optional[str] = Field(None, max_length=5)
payment_id: Optional[int] = None
class PedimentoPaymentsResponse(PedimentoPaymentsBase):
"""Schema for Pedimento Payments response"""
id: int
created_at: datetime
model_config = ConfigDict(from_attributes=True)

View File

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

View File

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

View File

@@ -0,0 +1,34 @@
from pydantic import BaseModel, Field, ConfigDict
from typing import Optional
from datetime import datetime
class PedimentoTransportMeansBase(BaseModel):
"""Base schema for Pedimento Transport Means"""
pedimento_id: int = Field(..., description="Pedimento ID")
tenant_id: int = Field(..., description="Tenant ID")
destination: Optional[int] = Field(None, description="Destination")
entry_exit: Optional[str] = Field(None, max_length=2, description="Entry/exit")
arrival: Optional[str] = Field(None, max_length=2, description="Arrival")
departure: Optional[str] = Field(None, max_length=2, description="Departure")
class PedimentoTransportMeansCreate(PedimentoTransportMeansBase):
"""Schema for creating a new Pedimento Transport Means"""
pass
class PedimentoTransportMeansUpdate(BaseModel):
"""Schema for updating a Pedimento Transport Means"""
destination: Optional[int] = None
entry_exit: Optional[str] = Field(None, max_length=2)
arrival: Optional[str] = Field(None, max_length=2)
departure: Optional[str] = Field(None, max_length=2)
class PedimentoTransportMeansResponse(PedimentoTransportMeansBase):
"""Schema for Pedimento Transport Means response"""
id: int
created_at: datetime
model_config = ConfigDict(from_attributes=True)

View File

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

View File

@@ -0,0 +1,54 @@
from pydantic import BaseModel, Field, ConfigDict
from typing import Optional
from decimal import Decimal
from datetime import datetime
class PedimentosBase(BaseModel):
"""Base schema for Pedimentos"""
year: Optional[str] = Field(None, max_length=2, description="Year")
customs_office: Optional[str] = Field(None, max_length=2, description="Customs office")
license: Optional[str] = Field(None, max_length=4, description="License")
pedimento_number: Optional[str] = Field(None, max_length=7, description="Pedimento number")
client_id: Optional[int] = Field(None, description="Client ID")
operation_type: Optional[int] = Field(None, description="Operation type")
pedimento_type: Optional[int] = Field(None, description="Pedimento type")
pedimento_key: Optional[str] = Field(None, max_length=2, description="Pedimento key")
regime: Optional[str] = Field(None, max_length=3, description="Regime")
status: Optional[str] = Field(None, max_length=30, description="Status")
usd_value: Optional[Decimal] = Field(None, description="USD value")
paid_price: Optional[Decimal] = Field(None, description="Paid price")
gross_weight: Optional[Decimal] = Field(None, description="Gross weight")
exchange_rate: Optional[Decimal] = Field(None, description="Exchange rate")
class PedimentosCreate(PedimentosBase):
"""Schema for creating a new Pedimento"""
pass
class PedimentosUpdate(BaseModel):
"""Schema for updating a Pedimento"""
year: Optional[str] = Field(None, max_length=2)
customs_office: Optional[str] = Field(None, max_length=2)
license: Optional[str] = Field(None, max_length=4)
pedimento_number: Optional[str] = Field(None, max_length=7)
client_id: Optional[int] = None
operation_type: Optional[int] = None
pedimento_type: Optional[int] = None
pedimento_key: Optional[str] = Field(None, max_length=2)
regime: Optional[str] = Field(None, max_length=3)
status: Optional[str] = Field(None, max_length=30)
usd_value: Optional[Decimal] = None
paid_price: Optional[Decimal] = None
gross_weight: Optional[Decimal] = None
exchange_rate: Optional[Decimal] = None
class PedimentosResponse(PedimentosBase):
"""Schema for Pedimento response"""
id: int
tenant_id: int
created_at: datetime
model_config = ConfigDict(from_attributes=True)

View File

@@ -0,0 +1,27 @@
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, PrimaryKeyConstraint, SmallInteger, String, UniqueConstraint, text
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped
from core.database import Base
class PedimentoConfigAdditional(Base):
__tablename__ = 'pedimento_config_additional'
__table_args__ = (
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id']),
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_config_additional'),
PrimaryKeyConstraint('id', name='pedimento_config_additional_pkey'),
UniqueConstraint('pedimento_id', name='pedimento_config_additional_pedimento_id_key'),
{'schema': 'a76'}
)
id = mapped_column(Integer)
pedimento_id = mapped_column(Integer, nullable=False)
tenant_id = mapped_column(Integer, nullable=False, index=True)
add_po_identifier = mapped_column(SmallInteger)
do_not_exempt_norms_complement_x = mapped_column(SmallInteger)
manual_pedimento_year = mapped_column(String(2))
enable_import_invoice_recipient = mapped_column(SmallInteger)
send_502_validation_file_for_consolidated = mapped_column(SmallInteger)
add_remove_norms = mapped_column(SmallInteger)
created_at = mapped_column(DateTime, server_default=text('CURRENT_TIMESTAMP'))
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_config_additional')

View File

@@ -0,0 +1,31 @@
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, PrimaryKeyConstraint, SmallInteger, String, UniqueConstraint, text
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped
from core.database import Base
class PedimentoConfigCalculations(Base):
__tablename__ = 'pedimento_config_calculations'
__table_args__ = (
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id']),
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_config_calculations'),
PrimaryKeyConstraint('id', name='pedimento_config_calculations_pkey'),
UniqueConstraint('pedimento_id', name='pedimento_config_calculations_pedimento_id_key'),
{'schema': 'a76'}
)
id = mapped_column(Integer)
pedimento_id = mapped_column(Integer, nullable=False)
tenant_id = mapped_column(Integer, nullable=False, index=True)
dta_type = mapped_column(String(1))
dta_operation = mapped_column(SmallInteger)
dta_vehicle_count = mapped_column(SmallInteger)
dta_mixed_rate_8permil = mapped_column(SmallInteger)
pays_vat = mapped_column(SmallInteger)
pays_prevalidation = mapped_column(SmallInteger)
include_sagar_certificate_fee = mapped_column(SmallInteger)
fixed_vehicle_dta_fee = mapped_column(SmallInteger)
additional_fixed_fee = mapped_column(SmallInteger)
additional_fixed_fee_payment_method = mapped_column(SmallInteger)
created_at = mapped_column(DateTime, server_default=text('CURRENT_TIMESTAMP'))
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_config_calculations')

View File

@@ -0,0 +1,33 @@
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, Numeric, PrimaryKeyConstraint, SmallInteger, UniqueConstraint, text
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped
from core.database import Base
class PedimentoConfigParameters(Base):
__tablename__ = 'pedimento_config_parameters'
__table_args__ = (
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id']),
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_config_parameters'),
PrimaryKeyConstraint('id', name='pedimento_config_parameters_pkey'),
UniqueConstraint('pedimento_id', name='pedimento_config_parameters_pedimento_id_key'),
{'schema': 'a76'}
)
id = mapped_column(Integer)
pedimento_id = mapped_column(Integer, nullable=False)
tenant_id = mapped_column(Integer, nullable=False, index=True)
is_embassy = mapped_column(SmallInteger)
embassy_dta = mapped_column(Numeric(11, 2))
rule_3121_section_ii = mapped_column(SmallInteger)
use_previous_tariff = mapped_column(SmallInteger)
use_payment_date_fi = mapped_column(SmallInteger)
add_state_supplier_record_505 = mapped_column(SmallInteger)
customs_value_calculation = mapped_column(SmallInteger)
two_decimals_unit_value = mapped_column(SmallInteger)
customs_value_per_item = mapped_column(SmallInteger)
is_national_supplier = mapped_column(SmallInteger)
is_consolidated = mapped_column(SmallInteger)
created_at = mapped_column(DateTime, server_default=text('CURRENT_TIMESTAMP'))
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_config_parameters')

View File

@@ -0,0 +1,27 @@
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, PrimaryKeyConstraint, SmallInteger, UniqueConstraint, text
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped
from core.database import Base
class PedimentoConfigSurcharges(Base):
__tablename__ = 'pedimento_config_surcharges'
__table_args__ = (
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id']),
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_config_surcharges'),
PrimaryKeyConstraint('id', name='pedimento_config_surcharges_pkey'),
UniqueConstraint('pedimento_id', name='pedimento_config_surcharges_pedimento_id_key'),
{'schema': 'a76'}
)
id = mapped_column(Integer)
pedimento_id = mapped_column(Integer, nullable=False)
tenant_id = mapped_column(Integer, nullable=False, index=True)
surcharge_igi = mapped_column(SmallInteger)
surcharge_dta = mapped_column(SmallInteger)
surcharge_vat = mapped_column(SmallInteger)
surcharge_isan = mapped_column(SmallInteger)
surcharge_ieps = mapped_column(SmallInteger)
surcharge_cc = mapped_column(SmallInteger)
created_at = mapped_column(DateTime, server_default=text('CURRENT_TIMESTAMP'))
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_config_surcharges')

View File

@@ -0,0 +1,26 @@
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, PrimaryKeyConstraint, SmallInteger, UniqueConstraint, text
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped
from core.database import Base
class PedimentoConfigUpdateRectification(Base):
__tablename__ = 'pedimento_config_update_rectification'
__table_args__ = (
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id']),
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_config_update_rectification'),
PrimaryKeyConstraint('id', name='pedimento_config_update_rectification_pkey'),
UniqueConstraint('pedimento_id', name='pedimento_config_update_rectification_pedimento_id_key'),
{'schema': 'a76'}
)
id = mapped_column(Integer)
pedimento_id = mapped_column(Integer, nullable=False)
tenant_id = mapped_column(Integer, nullable=False, index=True)
update_vat = mapped_column(SmallInteger)
update_advalorem = mapped_column(SmallInteger)
update_cc = mapped_column(SmallInteger)
update_ieps = mapped_column(SmallInteger)
calculate_surcharge = mapped_column(SmallInteger)
created_at = mapped_column(DateTime, server_default=text('CURRENT_TIMESTAMP'))
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_config_update_rectification')

View File

@@ -0,0 +1,25 @@
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, PrimaryKeyConstraint, SmallInteger, UniqueConstraint, text
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped
from core.database import Base
class PedimentoConfigUpdates(Base):
__tablename__ = 'pedimento_config_updates'
__table_args__ = (
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id']),
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_config_updates'),
PrimaryKeyConstraint('id', name='pedimento_config_updates_pkey'),
UniqueConstraint('pedimento_id', name='pedimento_config_updates_pedimento_id_key'),
{'schema': 'a76'}
)
id = mapped_column(Integer)
pedimento_id = mapped_column(Integer, nullable=False)
tenant_id = mapped_column(Integer, nullable=False, index=True)
update_vat = mapped_column(SmallInteger)
update_advalorem = mapped_column(SmallInteger)
update_cc = mapped_column(SmallInteger)
update_ieps = mapped_column(SmallInteger)
created_at = mapped_column(DateTime, server_default=text('CURRENT_TIMESTAMP'))
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_config_updates')

View File

@@ -0,0 +1,23 @@
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, PrimaryKeyConstraint, String, UniqueConstraint, text
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped
from core.database import Base
class PedimentoCustomsOffices(Base):
__tablename__ = 'pedimento_customs_offices'
__table_args__ = (
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id']),
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_customs_offices'),
PrimaryKeyConstraint('id', name='pedimento_customs_offices_pkey'),
UniqueConstraint('pedimento_id', name='pedimento_customs_offices_pedimento_id_key'),
{'schema': 'a76'}
)
id = mapped_column(Integer)
pedimento_id = mapped_column(Integer, nullable=False)
tenant_id = mapped_column(Integer, nullable=False, index=True)
dispatch_customs = mapped_column(String(3))
entry_exit_customs = mapped_column(String(3))
created_at = mapped_column(DateTime, server_default=text('CURRENT_TIMESTAMP'))
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_customs_offices')

View File

@@ -0,0 +1,34 @@
from sqlalchemy import DateTime, ForeignKeyConstraint, Index, Integer, PrimaryKeyConstraint, Time, UniqueConstraint, text
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped
from core.database import Base
class PedimentoDates(Base):
__tablename__ = 'pedimento_dates'
__table_args__ = (
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id']),
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_dates'),
PrimaryKeyConstraint('id', name='pedimento_dates_pkey'),
UniqueConstraint('pedimento_id', name='pedimento_dates_pedimento_id_key'),
Index('idx_pedimento_dates_pedimento_id', 'pedimento_id'),
{'schema': 'a76'}
)
id = mapped_column(Integer)
pedimento_id = mapped_column(Integer, nullable=False)
tenant_id = mapped_column(Integer, nullable=False, index=True)
entry_date = mapped_column(DateTime)
pedimento_date = mapped_column(DateTime)
payment_date = mapped_column(DateTime)
rectification_payment_date = mapped_column(DateTime)
extraction_date = mapped_column(DateTime)
submission_date = mapped_column(DateTime)
eucan_date = mapped_column(DateTime)
original_date = mapped_column(DateTime)
start_date = mapped_column(DateTime)
end_date = mapped_column(DateTime)
capture_date = mapped_column(DateTime)
capture_time = mapped_column(Time)
created_at = mapped_column(DateTime, server_default=text('CURRENT_TIMESTAMP'))
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_dates')

View File

@@ -0,0 +1,30 @@
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, Numeric, PrimaryKeyConstraint, SmallInteger, String, UniqueConstraint, text
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped
from core.database import Base
class PedimentoDecrementables(Base):
__tablename__ = 'pedimento_decrementables'
__table_args__ = (
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id']),
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_decrementables'),
PrimaryKeyConstraint('id', name='pedimento_decrementables_pkey'),
UniqueConstraint('pedimento_id', name='pedimento_decrementables_pedimento_id_key'),
{'schema': 'a76'}
)
id = mapped_column(Integer)
pedimento_id = mapped_column(Integer, nullable=False)
tenant_id = mapped_column(Integer, nullable=False, index=True)
freight = mapped_column(Numeric(13, 2))
insurance = mapped_column(Numeric(13, 2))
loading = mapped_column(Numeric(13, 2))
unloading = mapped_column(Numeric(13, 2))
others = mapped_column(Numeric(13, 2))
currency = mapped_column(String(3))
currency_factor = mapped_column(Numeric(15, 8))
not_affect_usd_value = mapped_column(SmallInteger)
not_affect_customs_value = mapped_column(SmallInteger)
created_at = mapped_column(DateTime, server_default=text('CURRENT_TIMESTAMP'))
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_decrementables')

View File

@@ -0,0 +1,31 @@
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, Numeric, PrimaryKeyConstraint, SmallInteger, String, UniqueConstraint, text
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped
from core.database import Base
class PedimentoIncrementables(Base):
__tablename__ = 'pedimento_incrementables'
__table_args__ = (
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id']),
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_incrementables'),
PrimaryKeyConstraint('id', name='pedimento_incrementables_pkey'),
UniqueConstraint('pedimento_id', name='pedimento_incrementables_pedimento_id_key'),
{'schema': 'a76'}
)
id = mapped_column(Integer)
pedimento_id = mapped_column(Integer, nullable=False)
tenant_id = mapped_column(Integer, nullable=False, index=True)
insured_value = mapped_column(Numeric(13, 2))
freight = mapped_column(Numeric(13, 2))
insurance = mapped_column(Numeric(13, 2))
packaging = mapped_column(Numeric(13, 2))
others = mapped_column(Numeric(13, 3))
deductibles = mapped_column(Numeric(13, 3))
currency = mapped_column(String(3))
currency_factor = mapped_column(Numeric(15, 8))
not_affect_usd_value = mapped_column(SmallInteger)
not_affect_customs_value = mapped_column(SmallInteger)
created_at = mapped_column(DateTime, server_default=text('CURRENT_TIMESTAMP'))
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_incrementables')

View File

@@ -0,0 +1,24 @@
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, Numeric, PrimaryKeyConstraint, SmallInteger, UniqueConstraint, text
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped
from core.database import Base
class PedimentoIndexes(Base):
__tablename__ = 'pedimento_indexes'
__table_args__ = (
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id']),
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_indexes'),
PrimaryKeyConstraint('id', name='pedimento_indexes_pkey'),
UniqueConstraint('pedimento_id', name='pedimento_indexes_pedimento_id_key'),
{'schema': 'a76'}
)
id = mapped_column(Integer)
pedimento_id = mapped_column(Integer, nullable=False)
tenant_id = mapped_column(Integer, nullable=False, index=True)
update_factor_type = mapped_column(SmallInteger)
update_factor = mapped_column(Numeric(7, 4))
manual_update_factor = mapped_column(SmallInteger)
created_at = mapped_column(DateTime, server_default=text('CURRENT_TIMESTAMP'))
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_indexes')

View File

@@ -0,0 +1,34 @@
from sqlalchemy import Date, DateTime, ForeignKeyConstraint, Index, Integer, PrimaryKeyConstraint, SmallInteger, String, Time, UniqueConstraint, text
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped
from core.database import Base
class PedimentoPayments(Base):
__tablename__ = 'pedimento_payments'
__table_args__ = (
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id']),
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_payments'),
PrimaryKeyConstraint('id', name='pedimento_payments_pkey'),
UniqueConstraint('pedimento_id', name='pedimento_payments_pedimento_id_key'),
Index('idx_pedimento_payments_pedimento_id', 'pedimento_id'),
{'schema': 'a76'}
)
id = mapped_column(Integer)
pedimento_id = mapped_column(Integer, nullable=False)
tenant_id = mapped_column(Integer, nullable=False, index=True)
acknowledgment = mapped_column(String(20))
operation_number = mapped_column(String(14))
bank_code = mapped_column(Integer)
cashier = mapped_column(String(2))
date = mapped_column(Date)
time = mapped_column(Time)
shift = mapped_column(String(1))
total_cash_paid = mapped_column(Integer)
total_contributions = mapped_column(Integer)
counter_payment = mapped_column(SmallInteger)
pece_code = mapped_column(String(5))
payment_id = mapped_column(Integer)
created_at = mapped_column(DateTime, server_default=text('CURRENT_TIMESTAMP'))
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_payments')

View File

@@ -0,0 +1,24 @@
from sqlalchemy import ForeignKeyConstraint, Integer, PrimaryKeyConstraint, String, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped
from core.database import Base
class PedimentoRectificationDestination(Base):
__tablename__ = 'pedimento_rectification_destination'
__table_args__ = (
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id']),
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_rectification_destination'),
PrimaryKeyConstraint('id', name='pedimento_rectification_destination_pkey'),
UniqueConstraint('pedimento_id', name='pedimento_rectification_destination_pedimento_id_key'),
{'schema': 'a76'}
)
id = mapped_column(Integer)
pedimento_id = mapped_column(Integer, nullable=False)
tenant_id = mapped_column(Integer, nullable=False, index=True)
destination_pedimento_year = mapped_column(String(2))
destination_customs_office = mapped_column(String(3))
destination_license = mapped_column(String(4))
destination_pedimento_number = mapped_column(String(7))
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_rectification_destination')

View File

@@ -0,0 +1,33 @@
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, PrimaryKeyConstraint, SmallInteger, String, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped
from core.database import Base
class PedimentoRectificationOrigin(Base):
__tablename__ = 'pedimento_rectification_origin'
__table_args__ = (
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id']),
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_rectification_origin'),
PrimaryKeyConstraint('id', name='pedimento_rectification_origin_pkey'),
UniqueConstraint('pedimento_id', name='pedimento_rectification_origin_pedimento_id_key'),
{'schema': 'a76'}
)
id = mapped_column(Integer)
pedimento_id = mapped_column(Integer, nullable=False)
tenant_id = mapped_column(Integer, nullable=False, index=True)
original_pedimento_year = mapped_column(String(2))
original_customs_office = mapped_column(String(3))
original_license = mapped_column(String(4))
original_pedimento_number = mapped_column(String(7))
original_pedimento_key = mapped_column(String(2))
original_payment_date = mapped_column(DateTime)
total_cash = mapped_column(Integer)
total_others = mapped_column(Integer)
reason = mapped_column(String(255))
charge_to_client = mapped_column(SmallInteger)
use_original_payment_date_for_interest_calc = mapped_column(SmallInteger)
manual_calculation = mapped_column(SmallInteger)
original_pedimento_norms = mapped_column(SmallInteger)
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_rectification_origin')

View File

@@ -0,0 +1,25 @@
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, PrimaryKeyConstraint, SmallInteger, String, UniqueConstraint, text
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped
from core.database import Base
class PedimentoTransportMeans(Base):
__tablename__ = 'pedimento_transport_means'
__table_args__ = (
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id']),
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_transport_means'),
PrimaryKeyConstraint('id', name='pedimento_transport_means_pkey'),
UniqueConstraint('pedimento_id', name='pedimento_transport_means_pedimento_id_key'),
{'schema': 'a76'}
)
id = mapped_column(Integer)
pedimento_id = mapped_column(Integer, nullable=False)
tenant_id = mapped_column(Integer, nullable=False, index=True)
destination = mapped_column(SmallInteger)
entry_exit = mapped_column(String(2))
arrival = mapped_column(String(2))
departure = mapped_column(String(2))
created_at = mapped_column(DateTime, server_default=text('CURRENT_TIMESTAMP'))
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_transport_means')

View File

@@ -0,0 +1,29 @@
from sqlalchemy import DateTime, ForeignKeyConstraint, Integer, PrimaryKeyConstraint, String, UniqueConstraint, text
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped
from core.database import Base
class PedimentoValidation(Base):
__tablename__ = 'pedimento_validation' #PedimentoValidacion
__table_args__ = (
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id']),
ForeignKeyConstraint(['pedimento_id'], ['a76.pedimentos.id'], ondelete='CASCADE', name='fk_pedimento_validation'),
PrimaryKeyConstraint('id', name='pedimento_validation_pkey'),
UniqueConstraint('pedimento_id', name='pedimento_validation_pedimento_id_key'),
{'schema': 'a76'}
)
id = mapped_column(Integer)
pedimento_id = mapped_column(Integer, nullable=False)
tenant_id = mapped_column(Integer, nullable=False, index=True)
validator = mapped_column(String(3)) #validador
validation_ack = mapped_column(String(8)) #acuse_validacion
pre_ack = mapped_column(String(8)) #acuse_previo
line_signature = mapped_column(String(50)) #firma_linea_captura
electronic_signature = mapped_column(String(999)) #firma_electronica
certificate_number = mapped_column(String(99)) #numero_certificado
validator_id = mapped_column(Integer)
responsible_id = mapped_column(Integer)
created_at = mapped_column(DateTime, server_default=text('CURRENT_TIMESTAMP'))
pedimento: Mapped['Pedimentos'] = relationship('Pedimentos', back_populates='pedimento_validation')

View File

@@ -0,0 +1,52 @@
from sqlalchemy import DateTime, ForeignKeyConstraint, Index, Integer, Numeric, PrimaryKeyConstraint, String, text
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.orm.base import Mapped
from core.database import Base
class Pedimentos(Base):
__tablename__ = 'pedimentos'
__table_args__ = (
ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id']),
PrimaryKeyConstraint('id', name='pedimentos_pkey'),
Index('idx_pedimentos_client_id', 'client_id'),
Index('idx_pedimentos_created_at', 'created_at'),
Index('idx_pedimentos_status', 'status'),
{'schema': 'a76'}
)
id = mapped_column(Integer)
tenant_id = mapped_column(Integer, nullable=False, index=True)
year = mapped_column(String(2))
customs_office = mapped_column(String(2))
license = mapped_column(String(4))
pedimento_number = mapped_column(String(7))
client_id = mapped_column(Integer)
operation_type = mapped_column(Integer)
pedimento_type = mapped_column(Integer)
pedimento_key = mapped_column(String(2))
regime = mapped_column(String(3))
status = mapped_column(String(30))
usd_value = mapped_column(Numeric(17, 6))
paid_price = mapped_column(Numeric(17, 6))
gross_weight = mapped_column(Numeric(19, 3))
exchange_rate = mapped_column(Numeric(9, 5))
created_at = mapped_column(DateTime, server_default=text('CURRENT_TIMESTAMP'))
pedimento_config_additional: Mapped['PedimentoConfigAdditional'] = relationship('PedimentoConfigAdditional', uselist=False, back_populates='pedimento')
pedimento_config_calculations: Mapped['PedimentoConfigCalculations'] = relationship('PedimentoConfigCalculations', uselist=False, back_populates='pedimento')
pedimento_config_parameters: Mapped['PedimentoConfigParameters'] = relationship('PedimentoConfigParameters', uselist=False, back_populates='pedimento')
pedimento_config_surcharges: Mapped['PedimentoConfigSurcharges'] = relationship('PedimentoConfigSurcharges', uselist=False, back_populates='pedimento')
pedimento_config_update_rectification: Mapped['PedimentoConfigUpdateRectification'] = relationship('PedimentoConfigUpdateRectification', uselist=False, back_populates='pedimento')
pedimento_config_updates: Mapped['PedimentoConfigUpdates'] = relationship('PedimentoConfigUpdates', uselist=False, back_populates='pedimento')
pedimento_customs_offices: Mapped['PedimentoCustomsOffices'] = relationship('PedimentoCustomsOffices', uselist=False, back_populates='pedimento')
pedimento_dates: Mapped['PedimentoDates'] = relationship('PedimentoDates', uselist=False, back_populates='pedimento')
pedimento_decrementables: Mapped['PedimentoDecrementables'] = relationship('PedimentoDecrementables', uselist=False, back_populates='pedimento')
pedimento_incrementables: Mapped['PedimentoIncrementables'] = relationship('PedimentoIncrementables', uselist=False, back_populates='pedimento')
pedimento_indexes: Mapped['PedimentoIndexes'] = relationship('PedimentoIndexes', uselist=False, back_populates='pedimento')
pedimento_payments: Mapped['PedimentoPayments'] = relationship('PedimentoPayments', uselist=False, back_populates='pedimento')
pedimento_rectification_destination: Mapped['PedimentoRectificationDestination'] = relationship('PedimentoRectificationDestination', uselist=False, back_populates='pedimento')
pedimento_rectification_origin: Mapped['PedimentoRectificationOrigin'] = relationship('PedimentoRectificationOrigin', uselist=False, back_populates='pedimento')
pedimento_transport_means: Mapped['PedimentoTransportMeans'] = relationship('PedimentoTransportMeans', uselist=False, back_populates='pedimento')
pedimento_validation: Mapped['PedimentoValidation'] = relationship('PedimentoValidation', uselist=False, back_populates='pedimento')

View File

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

View File

@@ -0,0 +1,94 @@
"""
Routes for PedimentoConfigAdditional CRUD operations
"""
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from core.database import get_core_db
from core.security import get_current_user, get_tenant_from_token
from ..services.pedimento_config_additional import PedimentoConfigAdditionalService
from ..dtos.pedimento_config_additional import (
PedimentoConfigAdditionalCreate,
PedimentoConfigAdditionalUpdate,
PedimentoConfigAdditionalResponse
)
router = APIRouter(prefix="/{pedimento_id}/config-additional")
@router.get("/", response_model=PedimentoConfigAdditionalResponse)
async def get_config_additional(
pedimento_id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Get config additional by pedimento ID"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
config = PedimentoConfigAdditionalService.get_by_pedimento_id(db, pedimento_id, tenant_id)
if not config:
raise HTTPException(status_code=404, detail="Config additional not found")
return config
@router.post("/", response_model=PedimentoConfigAdditionalResponse, status_code=201)
async def create_config_additional(
pedimento_id: int,
data: PedimentoConfigAdditionalCreate,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Create config additional"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
# Ensure pedimento_id and tenant_id match
if data.pedimento_id != pedimento_id:
raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
if data.tenant_id != tenant_id:
raise HTTPException(status_code=403, detail="Tenant ID mismatch")
config = PedimentoConfigAdditionalService.create(db, data)
return config
@router.put("/", response_model=PedimentoConfigAdditionalResponse)
async def update_config_additional(
pedimento_id: int,
data: PedimentoConfigAdditionalUpdate,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Update config additional"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
config = PedimentoConfigAdditionalService.update(db, pedimento_id, tenant_id, data)
if not config:
raise HTTPException(status_code=404, detail="Config additional not found")
return config
@router.delete("/", status_code=204)
async def delete_config_additional(
pedimento_id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Delete config additional"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
success = PedimentoConfigAdditionalService.delete(db, pedimento_id, tenant_id)
if not success:
raise HTTPException(status_code=404, detail="Config additional not found")
return None

View File

@@ -0,0 +1,92 @@
"""
Routes for PedimentoConfigCalculations CRUD operations
"""
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from core.database import get_core_db
from core.security import get_current_user, get_tenant_from_token
from ..services.pedimento_config_calculations import PedimentoConfigCalculationsService
from ..dtos.pedimento_config_calculations import (
PedimentoConfigCalculationsCreate,
PedimentoConfigCalculationsUpdate,
PedimentoConfigCalculationsResponse
)
router = APIRouter(prefix="/{pedimento_id}/config-calculations")
@router.get("/", response_model=PedimentoConfigCalculationsResponse)
async def get_config_calculations(
pedimento_id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Get config calculations by pedimento ID"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
config = PedimentoConfigCalculationsService.get_by_pedimento_id(db, pedimento_id, tenant_id)
if not config:
raise HTTPException(status_code=404, detail="Config calculations not found")
return config
@router.post("/", response_model=PedimentoConfigCalculationsResponse, status_code=201)
async def create_config_calculations(
pedimento_id: int,
data: PedimentoConfigCalculationsCreate,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Create config calculations"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
# Ensure pedimento_id matches
if data.pedimento_id != pedimento_id:
raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
config = PedimentoConfigCalculationsService.create(db, data, tenant_id)
return config
@router.put("/", response_model=PedimentoConfigCalculationsResponse)
async def update_config_calculations(
pedimento_id: int,
data: PedimentoConfigCalculationsUpdate,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Update config calculations"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
config = PedimentoConfigCalculationsService.update(db, pedimento_id, tenant_id, data)
if not config:
raise HTTPException(status_code=404, detail="Config calculations not found")
return config
@router.delete("/", status_code=204)
async def delete_config_calculations(
pedimento_id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Delete config calculations"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
success = PedimentoConfigCalculationsService.delete(db, pedimento_id, tenant_id)
if not success:
raise HTTPException(status_code=404, detail="Config calculations not found")
return None

View File

@@ -0,0 +1,92 @@
"""
Routes for PedimentoConfigParameters CRUD operations
"""
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from core.database import get_core_db
from core.security import get_current_user, get_tenant_from_token
from ..services.pedimento_config_parameters import PedimentoConfigParametersService
from ..dtos.pedimento_config_parameters import (
PedimentoConfigParametersCreate,
PedimentoConfigParametersUpdate,
PedimentoConfigParametersResponse
)
router = APIRouter(prefix="/{pedimento_id}/config-parameters")
@router.get("/", response_model=PedimentoConfigParametersResponse)
async def get_config_parameters(
pedimento_id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Get config parameters by pedimento ID"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
config = PedimentoConfigParametersService.get_by_pedimento_id(db, pedimento_id, tenant_id)
if not config:
raise HTTPException(status_code=404, detail="Config parameters not found")
return config
@router.post("/", response_model=PedimentoConfigParametersResponse, status_code=201)
async def create_config_parameters(
pedimento_id: int,
data: PedimentoConfigParametersCreate,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Create config parameters"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
# Ensure pedimento_id matches
if data.pedimento_id != pedimento_id:
raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
config = PedimentoConfigParametersService.create(db, data, tenant_id)
return config
@router.put("/", response_model=PedimentoConfigParametersResponse)
async def update_config_parameters(
pedimento_id: int,
data: PedimentoConfigParametersUpdate,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Update config parameters"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
config = PedimentoConfigParametersService.update(db, pedimento_id, tenant_id, data)
if not config:
raise HTTPException(status_code=404, detail="Config parameters not found")
return config
@router.delete("/", status_code=204)
async def delete_config_parameters(
pedimento_id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Delete config parameters"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
success = PedimentoConfigParametersService.delete(db, pedimento_id, tenant_id)
if not success:
raise HTTPException(status_code=404, detail="Config parameters not found")
return None

View File

@@ -0,0 +1,92 @@
"""
Routes for PedimentoConfigSurcharges CRUD operations
"""
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from core.database import get_core_db
from core.security import get_current_user, get_tenant_from_token
from ..services.pedimento_config_surcharges import PedimentoConfigSurchargesService
from ..dtos.pedimento_config_surcharges import (
PedimentoConfigSurchargesCreate,
PedimentoConfigSurchargesUpdate,
PedimentoConfigSurchargesResponse
)
router = APIRouter(prefix="/{pedimento_id}/config-surcharges")
@router.get("/", response_model=PedimentoConfigSurchargesResponse)
async def get_config_surcharges(
pedimento_id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Get config surcharges by pedimento ID"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
config = PedimentoConfigSurchargesService.get_by_pedimento_id(db, pedimento_id, tenant_id)
if not config:
raise HTTPException(status_code=404, detail="Config surcharges not found")
return config
@router.post("/", response_model=PedimentoConfigSurchargesResponse, status_code=201)
async def create_config_surcharges(
pedimento_id: int,
data: PedimentoConfigSurchargesCreate,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Create config surcharges"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
# Ensure pedimento_id matches
if data.pedimento_id != pedimento_id:
raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
config = PedimentoConfigSurchargesService.create(db, data, tenant_id)
return config
@router.put("/", response_model=PedimentoConfigSurchargesResponse)
async def update_config_surcharges(
pedimento_id: int,
data: PedimentoConfigSurchargesUpdate,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Update config surcharges"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
config = PedimentoConfigSurchargesService.update(db, pedimento_id, tenant_id, data)
if not config:
raise HTTPException(status_code=404, detail="Config surcharges not found")
return config
@router.delete("/", status_code=204)
async def delete_config_surcharges(
pedimento_id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Delete config surcharges"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
success = PedimentoConfigSurchargesService.delete(db, pedimento_id, tenant_id)
if not success:
raise HTTPException(status_code=404, detail="Config surcharges not found")
return None

View File

@@ -0,0 +1,92 @@
"""
Routes for PedimentoConfigUpdateRectification CRUD operations
"""
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from core.database import get_core_db
from core.security import get_current_user, get_tenant_from_token
from ..services.pedimento_config_update_rectification import PedimentoConfigUpdateRectificationService
from ..dtos.pedimento_config_update_rectification import (
PedimentoConfigUpdateRectificationCreate,
PedimentoConfigUpdateRectificationUpdate,
PedimentoConfigUpdateRectificationResponse
)
router = APIRouter(prefix="/{pedimento_id}/config-update-rectification")
@router.get("/", response_model=PedimentoConfigUpdateRectificationResponse)
async def get_config_update_rectification(
pedimento_id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Get config update rectification by pedimento ID"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
config = PedimentoConfigUpdateRectificationService.get_by_pedimento_id(db, pedimento_id, tenant_id)
if not config:
raise HTTPException(status_code=404, detail="Config update rectification not found")
return config
@router.post("/", response_model=PedimentoConfigUpdateRectificationResponse, status_code=201)
async def create_config_update_rectification(
pedimento_id: int,
data: PedimentoConfigUpdateRectificationCreate,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Create config update rectification"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
# Ensure pedimento_id matches
if data.pedimento_id != pedimento_id:
raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
config = PedimentoConfigUpdateRectificationService.create(db, data, tenant_id)
return config
@router.put("/", response_model=PedimentoConfigUpdateRectificationResponse)
async def update_config_update_rectification(
pedimento_id: int,
data: PedimentoConfigUpdateRectificationUpdate,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Update config update rectification"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
config = PedimentoConfigUpdateRectificationService.update(db, pedimento_id, tenant_id, data)
if not config:
raise HTTPException(status_code=404, detail="Config update rectification not found")
return config
@router.delete("/", status_code=204)
async def delete_config_update_rectification(
pedimento_id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Delete config update rectification"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
success = PedimentoConfigUpdateRectificationService.delete(db, pedimento_id, tenant_id)
if not success:
raise HTTPException(status_code=404, detail="Config update rectification not found")
return None

View File

@@ -0,0 +1,92 @@
"""
Routes for PedimentoConfigUpdates CRUD operations
"""
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from core.database import get_core_db
from core.security import get_current_user, get_tenant_from_token
from ..services.pedimento_config_updates import PedimentoConfigUpdatesService
from ..dtos.pedimento_config_updates import (
PedimentoConfigUpdatesCreate,
PedimentoConfigUpdatesUpdate,
PedimentoConfigUpdatesResponse
)
router = APIRouter(prefix="/{pedimento_id}/config-updates")
@router.get("/", response_model=PedimentoConfigUpdatesResponse)
async def get_config_updates(
pedimento_id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Get config updates by pedimento ID"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
config = PedimentoConfigUpdatesService.get_by_pedimento_id(db, pedimento_id, tenant_id)
if not config:
raise HTTPException(status_code=404, detail="Config updates not found")
return config
@router.post("/", response_model=PedimentoConfigUpdatesResponse, status_code=201)
async def create_config_updates(
pedimento_id: int,
data: PedimentoConfigUpdatesCreate,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Create config updates"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
# Ensure pedimento_id matches
if data.pedimento_id != pedimento_id:
raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
config = PedimentoConfigUpdatesService.create(db, data, tenant_id)
return config
@router.put("/", response_model=PedimentoConfigUpdatesResponse)
async def update_config_updates(
pedimento_id: int,
data: PedimentoConfigUpdatesUpdate,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Update config updates"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
config = PedimentoConfigUpdatesService.update(db, pedimento_id, tenant_id, data)
if not config:
raise HTTPException(status_code=404, detail="Config updates not found")
return config
@router.delete("/", status_code=204)
async def delete_config_updates(
pedimento_id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Delete config updates"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
success = PedimentoConfigUpdatesService.delete(db, pedimento_id, tenant_id)
if not success:
raise HTTPException(status_code=404, detail="Config updates not found")
return None

View File

@@ -0,0 +1,111 @@
"""
Routes for PedimentoCustomsOffices CRUD operations
"""
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from typing import List
from core.database import get_core_db
from core.security import get_current_user, get_tenant_from_token
from ..services.pedimento_customs_offices import PedimentoCustomsOfficesService
from ..dtos.pedimento_customs_offices import (
PedimentoCustomsOfficesCreate,
PedimentoCustomsOfficesUpdate,
PedimentoCustomsOfficesResponse
)
router = APIRouter(prefix="/{pedimento_id}/customs-offices")
@router.get("/", response_model=List[PedimentoCustomsOfficesResponse])
async def list_customs_offices(
pedimento_id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Get all customs offices for a pedimento"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
offices = PedimentoCustomsOfficesService.get_by_pedimento_id(db, pedimento_id, tenant_id)
return offices
@router.get("/{office_id}", response_model=PedimentoCustomsOfficesResponse)
async def get_customs_office(
pedimento_id: int,
office_id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Get a specific customs office by ID"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
office = PedimentoCustomsOfficesService.get_by_id(db, office_id, pedimento_id, tenant_id)
if not office:
raise HTTPException(status_code=404, detail="Customs office not found")
return office
@router.post("/", response_model=PedimentoCustomsOfficesResponse, status_code=201)
async def create_customs_office(
pedimento_id: int,
data: PedimentoCustomsOfficesCreate,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Create a new customs office"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
# Ensure pedimento_id matches
if data.pedimento_id != pedimento_id:
raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
office = PedimentoCustomsOfficesService.create(db, data, tenant_id)
return office
@router.put("/{office_id}", response_model=PedimentoCustomsOfficesResponse)
async def update_customs_office(
pedimento_id: int,
office_id: int,
data: PedimentoCustomsOfficesUpdate,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Update a customs office"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
office = PedimentoCustomsOfficesService.update(db, office_id, pedimento_id, tenant_id, data)
if not office:
raise HTTPException(status_code=404, detail="Customs office not found")
return office
@router.delete("/{office_id}", status_code=204)
async def delete_customs_office(
pedimento_id: int,
office_id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Delete a customs office"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
success = PedimentoCustomsOfficesService.delete(db, office_id, pedimento_id, tenant_id)
if not success:
raise HTTPException(status_code=404, detail="Customs office not found")
return None

View File

@@ -0,0 +1,92 @@
"""
Routes for PedimentoDates CRUD operations
"""
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from core.database import get_core_db
from core.security import get_current_user, get_tenant_from_token
from ..services.pedimento_dates import PedimentoDatesService
from ..dtos.pedimento_dates import (
PedimentoDatesCreate,
PedimentoDatesUpdate,
PedimentoDatesResponse
)
router = APIRouter(prefix="/{pedimento_id}/dates")
@router.get("/", response_model=PedimentoDatesResponse)
async def get_dates(
pedimento_id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Get dates by pedimento ID"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
dates = PedimentoDatesService.get_by_pedimento_id(db, pedimento_id, tenant_id)
if not dates:
raise HTTPException(status_code=404, detail="Pedimento dates not found")
return dates
@router.post("/", response_model=PedimentoDatesResponse, status_code=201)
async def create_dates(
pedimento_id: int,
data: PedimentoDatesCreate,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Create pedimento dates"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
# Ensure pedimento_id matches
if data.pedimento_id != pedimento_id:
raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
dates = PedimentoDatesService.create(db, data, tenant_id)
return dates
@router.put("/", response_model=PedimentoDatesResponse)
async def update_dates(
pedimento_id: int,
data: PedimentoDatesUpdate,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Update pedimento dates"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
dates = PedimentoDatesService.update(db, pedimento_id, tenant_id, data)
if not dates:
raise HTTPException(status_code=404, detail="Pedimento dates not found")
return dates
@router.delete("/", status_code=204)
async def delete_dates(
pedimento_id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Delete pedimento dates"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
success = PedimentoDatesService.delete(db, pedimento_id, tenant_id)
if not success:
raise HTTPException(status_code=404, detail="Pedimento dates not found")
return None

View File

@@ -0,0 +1,111 @@
"""
Routes for PedimentoDecrementables CRUD operations
"""
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from typing import List
from core.database import get_core_db
from core.security import get_current_user, get_tenant_from_token
from ..services.pedimento_decrementables import PedimentoDecrementablesService
from ..dtos.pedimento_decrementables import (
PedimentoDecrementablesCreate,
PedimentoDecrementablesUpdate,
PedimentoDecrementablesResponse
)
router = APIRouter(prefix="/{pedimento_id}/decrementables")
@router.get("/", response_model=List[PedimentoDecrementablesResponse])
async def list_decrementables(
pedimento_id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Get all decrementables for a pedimento"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
decrementables = PedimentoDecrementablesService.get_by_pedimento_id(db, pedimento_id, tenant_id)
return decrementables
@router.get("/{decrementable_id}", response_model=PedimentoDecrementablesResponse)
async def get_decrementable(
pedimento_id: int,
decrementable_id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Get a specific decrementable by ID"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
decrementable = PedimentoDecrementablesService.get_by_id(db, decrementable_id, pedimento_id, tenant_id)
if not decrementable:
raise HTTPException(status_code=404, detail="Decrementable not found")
return decrementable
@router.post("/", response_model=PedimentoDecrementablesResponse, status_code=201)
async def create_decrementable(
pedimento_id: int,
data: PedimentoDecrementablesCreate,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Create a new decrementable"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
# Ensure pedimento_id matches
if data.pedimento_id != pedimento_id:
raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
decrementable = PedimentoDecrementablesService.create(db, data, tenant_id)
return decrementable
@router.put("/{decrementable_id}", response_model=PedimentoDecrementablesResponse)
async def update_decrementable(
pedimento_id: int,
decrementable_id: int,
data: PedimentoDecrementablesUpdate,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Update a decrementable"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
decrementable = PedimentoDecrementablesService.update(db, decrementable_id, pedimento_id, tenant_id, data)
if not decrementable:
raise HTTPException(status_code=404, detail="Decrementable not found")
return decrementable
@router.delete("/{decrementable_id}", status_code=204)
async def delete_decrementable(
pedimento_id: int,
decrementable_id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Delete a decrementable"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
success = PedimentoDecrementablesService.delete(db, decrementable_id, pedimento_id, tenant_id)
if not success:
raise HTTPException(status_code=404, detail="Decrementable not found")
return None

View File

@@ -0,0 +1,111 @@
"""
Routes for PedimentoIncrementables CRUD operations
"""
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from typing import List
from core.database import get_core_db
from core.security import get_current_user, get_tenant_from_token
from ..services.pedimento_incrementables import PedimentoIncrementablesService
from ..dtos.pedimento_incrementables import (
PedimentoIncrementablesCreate,
PedimentoIncrementablesUpdate,
PedimentoIncrementablesResponse
)
router = APIRouter(prefix="/{pedimento_id}/incrementables")
@router.get("/", response_model=List[PedimentoIncrementablesResponse])
async def list_incrementables(
pedimento_id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Get all incrementables for a pedimento"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
incrementables = PedimentoIncrementablesService.get_by_pedimento_id(db, pedimento_id, tenant_id)
return incrementables
@router.get("/{incrementable_id}", response_model=PedimentoIncrementablesResponse)
async def get_incrementable(
pedimento_id: int,
incrementable_id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Get a specific incrementable by ID"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
incrementable = PedimentoIncrementablesService.get_by_id(db, incrementable_id, pedimento_id, tenant_id)
if not incrementable:
raise HTTPException(status_code=404, detail="Incrementable not found")
return incrementable
@router.post("/", response_model=PedimentoIncrementablesResponse, status_code=201)
async def create_incrementable(
pedimento_id: int,
data: PedimentoIncrementablesCreate,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Create a new incrementable"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
# Ensure pedimento_id matches
if data.pedimento_id != pedimento_id:
raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
incrementable = PedimentoIncrementablesService.create(db, data, tenant_id)
return incrementable
@router.put("/{incrementable_id}", response_model=PedimentoIncrementablesResponse)
async def update_incrementable(
pedimento_id: int,
incrementable_id: int,
data: PedimentoIncrementablesUpdate,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Update an incrementable"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
incrementable = PedimentoIncrementablesService.update(db, incrementable_id, pedimento_id, tenant_id, data)
if not incrementable:
raise HTTPException(status_code=404, detail="Incrementable not found")
return incrementable
@router.delete("/{incrementable_id}", status_code=204)
async def delete_incrementable(
pedimento_id: int,
incrementable_id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Delete an incrementable"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
success = PedimentoIncrementablesService.delete(db, incrementable_id, pedimento_id, tenant_id)
if not success:
raise HTTPException(status_code=404, detail="Incrementable not found")
return None

View File

@@ -0,0 +1,92 @@
"""
Routes for PedimentoIndexes CRUD operations
"""
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from core.database import get_core_db
from core.security import get_current_user, get_tenant_from_token
from ..services.pedimento_indexes import PedimentoIndexesService
from ..dtos.pedimento_indexes import (
PedimentoIndexesCreate,
PedimentoIndexesUpdate,
PedimentoIndexesResponse
)
router = APIRouter(prefix="/{pedimento_id}/indexes")
@router.get("/", response_model=PedimentoIndexesResponse)
async def get_indexes(
pedimento_id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Get indexes by pedimento ID"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
indexes = PedimentoIndexesService.get_by_pedimento_id(db, pedimento_id, tenant_id)
if not indexes:
raise HTTPException(status_code=404, detail="Pedimento indexes not found")
return indexes
@router.post("/", response_model=PedimentoIndexesResponse, status_code=201)
async def create_indexes(
pedimento_id: int,
data: PedimentoIndexesCreate,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Create pedimento indexes"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
# Ensure pedimento_id matches
if data.pedimento_id != pedimento_id:
raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
indexes = PedimentoIndexesService.create(db, data, tenant_id)
return indexes
@router.put("/", response_model=PedimentoIndexesResponse)
async def update_indexes(
pedimento_id: int,
data: PedimentoIndexesUpdate,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Update pedimento indexes"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
indexes = PedimentoIndexesService.update(db, pedimento_id, tenant_id, data)
if not indexes:
raise HTTPException(status_code=404, detail="Pedimento indexes not found")
return indexes
@router.delete("/", status_code=204)
async def delete_indexes(
pedimento_id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Delete pedimento indexes"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
success = PedimentoIndexesService.delete(db, pedimento_id, tenant_id)
if not success:
raise HTTPException(status_code=404, detail="Pedimento indexes not found")
return None

View File

@@ -0,0 +1,111 @@
"""
Routes for PedimentoPayments CRUD operations
"""
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from typing import List
from core.database import get_core_db
from core.security import get_current_user, get_tenant_from_token
from ..services.pedimento_payments import PedimentoPaymentsService
from ..dtos.pedimento_payments import (
PedimentoPaymentsCreate,
PedimentoPaymentsUpdate,
PedimentoPaymentsResponse
)
router = APIRouter(prefix="/{pedimento_id}/payments")
@router.get("/", response_model=List[PedimentoPaymentsResponse])
async def list_payments(
pedimento_id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Get all payments for a pedimento"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
payments = PedimentoPaymentsService.get_by_pedimento_id(db, pedimento_id, tenant_id)
return payments
@router.get("/{payment_id}", response_model=PedimentoPaymentsResponse)
async def get_payment(
pedimento_id: int,
payment_id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Get a specific payment by ID"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
payment = PedimentoPaymentsService.get_by_id(db, payment_id, pedimento_id, tenant_id)
if not payment:
raise HTTPException(status_code=404, detail="Payment not found")
return payment
@router.post("/", response_model=PedimentoPaymentsResponse, status_code=201)
async def create_payment(
pedimento_id: int,
data: PedimentoPaymentsCreate,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Create a new payment"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
# Ensure pedimento_id matches
if data.pedimento_id != pedimento_id:
raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
payment = PedimentoPaymentsService.create(db, data, tenant_id)
return payment
@router.put("/{payment_id}", response_model=PedimentoPaymentsResponse)
async def update_payment(
pedimento_id: int,
payment_id: int,
data: PedimentoPaymentsUpdate,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Update a payment"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
payment = PedimentoPaymentsService.update(db, payment_id, pedimento_id, tenant_id, data)
if not payment:
raise HTTPException(status_code=404, detail="Payment not found")
return payment
@router.delete("/{payment_id}", status_code=204)
async def delete_payment(
pedimento_id: int,
payment_id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Delete a payment"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
success = PedimentoPaymentsService.delete(db, payment_id, pedimento_id, tenant_id)
if not success:
raise HTTPException(status_code=404, detail="Payment not found")
return None

View File

@@ -0,0 +1,92 @@
"""
Routes for PedimentoRectificationDestination CRUD operations
"""
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from core.database import get_core_db
from core.security import get_current_user, get_tenant_from_token
from ..services.pedimento_rectification_destination import PedimentoRectificationDestinationService
from ..dtos.pedimento_rectification_destination import (
PedimentoRectificationDestinationCreate,
PedimentoRectificationDestinationUpdate,
PedimentoRectificationDestinationResponse
)
router = APIRouter(prefix="/{pedimento_id}/rectification-destination")
@router.get("/", response_model=PedimentoRectificationDestinationResponse)
async def get_rectification_destination(
pedimento_id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Get rectification destination by pedimento ID"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
rectification = PedimentoRectificationDestinationService.get_by_pedimento_id(db, pedimento_id, tenant_id)
if not rectification:
raise HTTPException(status_code=404, detail="Rectification destination not found")
return rectification
@router.post("/", response_model=PedimentoRectificationDestinationResponse, status_code=201)
async def create_rectification_destination(
pedimento_id: int,
data: PedimentoRectificationDestinationCreate,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Create rectification destination"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
# Ensure pedimento_id matches
if data.pedimento_id != pedimento_id:
raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
rectification = PedimentoRectificationDestinationService.create(db, data, tenant_id)
return rectification
@router.put("/", response_model=PedimentoRectificationDestinationResponse)
async def update_rectification_destination(
pedimento_id: int,
data: PedimentoRectificationDestinationUpdate,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Update rectification destination"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
rectification = PedimentoRectificationDestinationService.update(db, pedimento_id, tenant_id, data)
if not rectification:
raise HTTPException(status_code=404, detail="Rectification destination not found")
return rectification
@router.delete("/", status_code=204)
async def delete_rectification_destination(
pedimento_id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Delete rectification destination"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
success = PedimentoRectificationDestinationService.delete(db, pedimento_id, tenant_id)
if not success:
raise HTTPException(status_code=404, detail="Rectification destination not found")
return None

View File

@@ -0,0 +1,92 @@
"""
Routes for PedimentoRectificationOrigin CRUD operations
"""
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from core.database import get_core_db
from core.security import get_current_user, get_tenant_from_token
from ..services.pedimento_rectification_origin import PedimentoRectificationOriginService
from ..dtos.pedimento_rectification_origin import (
PedimentoRectificationOriginCreate,
PedimentoRectificationOriginUpdate,
PedimentoRectificationOriginResponse
)
router = APIRouter(prefix="/{pedimento_id}/rectification-origin")
@router.get("/", response_model=PedimentoRectificationOriginResponse)
async def get_rectification_origin(
pedimento_id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Get rectification origin by pedimento ID"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
rectification = PedimentoRectificationOriginService.get_by_pedimento_id(db, pedimento_id, tenant_id)
if not rectification:
raise HTTPException(status_code=404, detail="Rectification origin not found")
return rectification
@router.post("/", response_model=PedimentoRectificationOriginResponse, status_code=201)
async def create_rectification_origin(
pedimento_id: int,
data: PedimentoRectificationOriginCreate,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Create rectification origin"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
# Ensure pedimento_id matches
if data.pedimento_id != pedimento_id:
raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
rectification = PedimentoRectificationOriginService.create(db, data, tenant_id)
return rectification
@router.put("/", response_model=PedimentoRectificationOriginResponse)
async def update_rectification_origin(
pedimento_id: int,
data: PedimentoRectificationOriginUpdate,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Update rectification origin"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
rectification = PedimentoRectificationOriginService.update(db, pedimento_id, tenant_id, data)
if not rectification:
raise HTTPException(status_code=404, detail="Rectification origin not found")
return rectification
@router.delete("/", status_code=204)
async def delete_rectification_origin(
pedimento_id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Delete rectification origin"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
success = PedimentoRectificationOriginService.delete(db, pedimento_id, tenant_id)
if not success:
raise HTTPException(status_code=404, detail="Rectification origin not found")
return None

View File

@@ -0,0 +1,111 @@
"""
Routes for PedimentoTransportMeans CRUD operations
"""
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from typing import List
from core.database import get_core_db
from core.security import get_current_user, get_tenant_from_token
from ..services.pedimento_transport_means import PedimentoTransportMeansService
from ..dtos.pedimento_transport_means import (
PedimentoTransportMeansCreate,
PedimentoTransportMeansUpdate,
PedimentoTransportMeansResponse
)
router = APIRouter(prefix="/{pedimento_id}/transport-means")
@router.get("/", response_model=List[PedimentoTransportMeansResponse])
async def list_transport_means(
pedimento_id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Get all transport means for a pedimento"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
transport_means = PedimentoTransportMeansService.get_by_pedimento_id(db, pedimento_id, tenant_id)
return transport_means
@router.get("/{transport_mean_id}", response_model=PedimentoTransportMeansResponse)
async def get_transport_mean(
pedimento_id: int,
transport_mean_id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Get a specific transport mean by ID"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
transport_mean = PedimentoTransportMeansService.get_by_id(db, transport_mean_id, pedimento_id, tenant_id)
if not transport_mean:
raise HTTPException(status_code=404, detail="Transport mean not found")
return transport_mean
@router.post("/", response_model=PedimentoTransportMeansResponse, status_code=201)
async def create_transport_mean(
pedimento_id: int,
data: PedimentoTransportMeansCreate,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Create a new transport mean"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
# Ensure pedimento_id matches
if data.pedimento_id != pedimento_id:
raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
transport_mean = PedimentoTransportMeansService.create(db, data, tenant_id)
return transport_mean
@router.put("/{transport_mean_id}", response_model=PedimentoTransportMeansResponse)
async def update_transport_mean(
pedimento_id: int,
transport_mean_id: int,
data: PedimentoTransportMeansUpdate,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Update a transport mean"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
transport_mean = PedimentoTransportMeansService.update(db, transport_mean_id, pedimento_id, tenant_id, data)
if not transport_mean:
raise HTTPException(status_code=404, detail="Transport mean not found")
return transport_mean
@router.delete("/{transport_mean_id}", status_code=204)
async def delete_transport_mean(
pedimento_id: int,
transport_mean_id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Delete a transport mean"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
success = PedimentoTransportMeansService.delete(db, transport_mean_id, pedimento_id, tenant_id)
if not success:
raise HTTPException(status_code=404, detail="Transport mean not found")
return None

View File

@@ -0,0 +1,92 @@
"""
Routes for PedimentoValidation CRUD operations
"""
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from core.database import get_core_db
from core.security import get_current_user, get_tenant_from_token
from ..services.pedimento_validation import PedimentoValidationService
from ..dtos.pedimento_validation import (
PedimentoValidationCreate,
PedimentoValidationUpdate,
PedimentoValidationResponse
)
router = APIRouter(prefix="/{pedimento_id}/validation")
@router.get("/", response_model=PedimentoValidationResponse)
async def get_validation(
pedimento_id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Get validation by pedimento ID"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
validation = PedimentoValidationService.get_by_pedimento_id(db, pedimento_id, tenant_id)
if not validation:
raise HTTPException(status_code=404, detail="Pedimento validation not found")
return validation
@router.post("/", response_model=PedimentoValidationResponse, status_code=201)
async def create_validation(
pedimento_id: int,
data: PedimentoValidationCreate,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Create pedimento validation"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
# Ensure pedimento_id matches
if data.pedimento_id != pedimento_id:
raise HTTPException(status_code=400, detail="Pedimento ID mismatch")
validation = PedimentoValidationService.create(db, data, tenant_id)
return validation
@router.put("/", response_model=PedimentoValidationResponse)
async def update_validation(
pedimento_id: int,
data: PedimentoValidationUpdate,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Update pedimento validation"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
validation = PedimentoValidationService.update(db, pedimento_id, tenant_id, data)
if not validation:
raise HTTPException(status_code=404, detail="Pedimento validation not found")
return validation
@router.delete("/", status_code=204)
async def delete_validation(
pedimento_id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Delete pedimento validation"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
success = PedimentoValidationService.delete(db, pedimento_id, tenant_id)
if not success:
raise HTTPException(status_code=404, detail="Pedimento validation not found")
return None

View File

@@ -0,0 +1,118 @@
"""
Routes for Pedimentos CRUD operations
"""
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from typing import Dict, Any, Optional
from core.database import get_core_db
from core.security import get_current_user, get_tenant_from_token
from ..services.pedimentos import PedimentosService
from ..dtos.pedimentos import PedimentosCreate, PedimentosUpdate, PedimentosResponse
router = APIRouter()
@router.get("/", response_model=Dict[str, Any])
async def list_pedimentos(
page: int = Query(1, ge=1, description="Page number"),
page_size: int = Query(50, ge=1, le=100, description="Page size"),
status: Optional[str] = Query(None, description="Filter by status"),
client_id: Optional[int] = Query(None, description="Filter by client ID"),
year: Optional[str] = Query(None, description="Filter by year"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Get all pedimentos with pagination and filters"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
filters = {}
if status:
filters["status"] = status
if client_id:
filters["client_id"] = client_id
if year:
filters["year"] = year
skip = (page - 1) * page_size
items, total = PedimentosService.get_all(db, tenant_id, skip, page_size, filters)
return {
"items": [PedimentosResponse.model_validate(item) for item in items],
"total": total,
"page": page,
"page_size": page_size
}
@router.get("/{pedimento_id}", response_model=PedimentosResponse)
async def get_pedimento(
pedimento_id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Get a pedimento by ID"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
pedimento = PedimentosService.get_by_id(db, pedimento_id, tenant_id)
if not pedimento:
raise HTTPException(status_code=404, detail="Pedimento not found")
return pedimento
@router.post("/", response_model=PedimentosResponse, status_code=201)
async def create_pedimento(
data: PedimentosCreate,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Create a new pedimento"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
pedimento = PedimentosService.create(db, data, tenant_id)
return pedimento
@router.put("/{pedimento_id}", response_model=PedimentosResponse)
async def update_pedimento(
pedimento_id: int,
data: PedimentosUpdate,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Update a pedimento"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
pedimento = PedimentosService.update(db, pedimento_id, tenant_id, data)
if not pedimento:
raise HTTPException(status_code=404, detail="Pedimento not found")
return pedimento
@router.delete("/{pedimento_id}", status_code=204)
async def delete_pedimento(
pedimento_id: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""Delete a pedimento"""
tenant_id = get_tenant_from_token(current_user)
if not tenant_id:
raise HTTPException(status_code=400, detail="Tenant ID not found in token")
success = PedimentosService.delete(db, pedimento_id, tenant_id)
if not success:
raise HTTPException(status_code=404, detail="Pedimento not found")
return None

View File

@@ -0,0 +1,60 @@
"""
Service layer for PedimentoConfigAdditional CRUD operations
"""
from typing import Optional
from sqlalchemy.orm import Session
from ..models.pedimento_config_additional import PedimentoConfigAdditional
from ..dtos.pedimento_config_additional import PedimentoConfigAdditionalCreate, PedimentoConfigAdditionalUpdate
class PedimentoConfigAdditionalService:
"""Service class for PedimentoConfigAdditional business logic"""
@staticmethod
def get_by_pedimento_id(db: Session, pedimento_id: int, tenant_id: int) -> Optional[PedimentoConfigAdditional]:
"""Get config by pedimento ID"""
return db.query(PedimentoConfigAdditional).filter(
PedimentoConfigAdditional.pedimento_id == pedimento_id,
PedimentoConfigAdditional.tenant_id == tenant_id
).first()
@staticmethod
def create(db: Session, config_data: PedimentoConfigAdditionalCreate) -> PedimentoConfigAdditional:
"""Create a new config"""
config = PedimentoConfigAdditional(**config_data.model_dump())
db.add(config)
db.commit()
db.refresh(config)
return config
@staticmethod
def update(
db: Session,
pedimento_id: int,
tenant_id: int,
config_data: PedimentoConfigAdditionalUpdate
) -> Optional[PedimentoConfigAdditional]:
"""Update config"""
config = PedimentoConfigAdditionalService.get_by_pedimento_id(db, pedimento_id, tenant_id)
if not config:
return None
update_data = config_data.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(config, field, value)
db.commit()
db.refresh(config)
return config
@staticmethod
def delete(db: Session, pedimento_id: int, tenant_id: int) -> bool:
"""Delete config"""
config = PedimentoConfigAdditionalService.get_by_pedimento_id(db, pedimento_id, tenant_id)
if not config:
return False
db.delete(config)
db.commit()
return True

View File

@@ -0,0 +1,60 @@
"""
Service layer for PedimentoConfigCalculations CRUD operations
"""
from typing import Optional
from sqlalchemy.orm import Session
from ..models.pedimento_config_calculations import PedimentoConfigCalculations
from ..dtos.pedimento_config_calculations import PedimentoConfigCalculationsCreate, PedimentoConfigCalculationsUpdate
class PedimentoConfigCalculationsService:
"""Service class for PedimentoConfigCalculations business logic"""
@staticmethod
def get_by_pedimento_id(db: Session, pedimento_id: int, tenant_id: int) -> Optional[PedimentoConfigCalculations]:
"""Get config by pedimento ID"""
return db.query(PedimentoConfigCalculations).filter(
PedimentoConfigCalculations.pedimento_id == pedimento_id,
PedimentoConfigCalculations.tenant_id == tenant_id
).first()
@staticmethod
def create(db: Session, config_data: PedimentoConfigCalculationsCreate) -> PedimentoConfigCalculations:
"""Create a new config"""
config = PedimentoConfigCalculations(**config_data.model_dump())
db.add(config)
db.commit()
db.refresh(config)
return config
@staticmethod
def update(
db: Session,
pedimento_id: int,
tenant_id: int,
config_data: PedimentoConfigCalculationsUpdate
) -> Optional[PedimentoConfigCalculations]:
"""Update config"""
config = PedimentoConfigCalculationsService.get_by_pedimento_id(db, pedimento_id, tenant_id)
if not config:
return None
update_data = config_data.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(config, field, value)
db.commit()
db.refresh(config)
return config
@staticmethod
def delete(db: Session, pedimento_id: int, tenant_id: int) -> bool:
"""Delete config"""
config = PedimentoConfigCalculationsService.get_by_pedimento_id(db, pedimento_id, tenant_id)
if not config:
return False
db.delete(config)
db.commit()
return True

View File

@@ -0,0 +1,60 @@
"""
Service layer for PedimentoConfigParameters CRUD operations
"""
from typing import Optional
from sqlalchemy.orm import Session
from ..models.pedimento_config_parameters import PedimentoConfigParameters
from ..dtos.pedimento_config_parameters import PedimentoConfigParametersCreate, PedimentoConfigParametersUpdate
class PedimentoConfigParametersService:
"""Service class for PedimentoConfigParameters business logic"""
@staticmethod
def get_by_pedimento_id(db: Session, pedimento_id: int, tenant_id: int) -> Optional[PedimentoConfigParameters]:
"""Get config by pedimento ID"""
return db.query(PedimentoConfigParameters).filter(
PedimentoConfigParameters.pedimento_id == pedimento_id,
PedimentoConfigParameters.tenant_id == tenant_id
).first()
@staticmethod
def create(db: Session, config_data: PedimentoConfigParametersCreate) -> PedimentoConfigParameters:
"""Create a new config"""
config = PedimentoConfigParameters(**config_data.model_dump())
db.add(config)
db.commit()
db.refresh(config)
return config
@staticmethod
def update(
db: Session,
pedimento_id: int,
tenant_id: int,
config_data: PedimentoConfigParametersUpdate
) -> Optional[PedimentoConfigParameters]:
"""Update config"""
config = PedimentoConfigParametersService.get_by_pedimento_id(db, pedimento_id, tenant_id)
if not config:
return None
update_data = config_data.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(config, field, value)
db.commit()
db.refresh(config)
return config
@staticmethod
def delete(db: Session, pedimento_id: int, tenant_id: int) -> bool:
"""Delete config"""
config = PedimentoConfigParametersService.get_by_pedimento_id(db, pedimento_id, tenant_id)
if not config:
return False
db.delete(config)
db.commit()
return True

View File

@@ -0,0 +1,60 @@
"""
Service layer for PedimentoConfigSurcharges CRUD operations
"""
from typing import Optional
from sqlalchemy.orm import Session
from ..models.pedimento_config_surcharges import PedimentoConfigSurcharges
from ..dtos.pedimento_config_surcharges import PedimentoConfigSurchargesCreate, PedimentoConfigSurchargesUpdate
class PedimentoConfigSurchargesService:
"""Service class for PedimentoConfigSurcharges business logic"""
@staticmethod
def get_by_pedimento_id(db: Session, pedimento_id: int, tenant_id: int) -> Optional[PedimentoConfigSurcharges]:
"""Get config by pedimento ID"""
return db.query(PedimentoConfigSurcharges).filter(
PedimentoConfigSurcharges.pedimento_id == pedimento_id,
PedimentoConfigSurcharges.tenant_id == tenant_id
).first()
@staticmethod
def create(db: Session, config_data: PedimentoConfigSurchargesCreate) -> PedimentoConfigSurcharges:
"""Create a new config"""
config = PedimentoConfigSurcharges(**config_data.model_dump())
db.add(config)
db.commit()
db.refresh(config)
return config
@staticmethod
def update(
db: Session,
pedimento_id: int,
tenant_id: int,
config_data: PedimentoConfigSurchargesUpdate
) -> Optional[PedimentoConfigSurcharges]:
"""Update config"""
config = PedimentoConfigSurchargesService.get_by_pedimento_id(db, pedimento_id, tenant_id)
if not config:
return None
update_data = config_data.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(config, field, value)
db.commit()
db.refresh(config)
return config
@staticmethod
def delete(db: Session, pedimento_id: int, tenant_id: int) -> bool:
"""Delete config"""
config = PedimentoConfigSurchargesService.get_by_pedimento_id(db, pedimento_id, tenant_id)
if not config:
return False
db.delete(config)
db.commit()
return True

View File

@@ -0,0 +1,60 @@
"""
Service layer for PedimentoConfigUpdateRectification CRUD operations
"""
from typing import Optional
from sqlalchemy.orm import Session
from ..models.pedimento_config_update_rectification import PedimentoConfigUpdateRectification
from ..dtos.pedimento_config_update_rectification import PedimentoConfigUpdateRectificationCreate, PedimentoConfigUpdateRectificationUpdate
class PedimentoConfigUpdateRectificationService:
"""Service class for PedimentoConfigUpdateRectification business logic"""
@staticmethod
def get_by_pedimento_id(db: Session, pedimento_id: int, tenant_id: int) -> Optional[PedimentoConfigUpdateRectification]:
"""Get config by pedimento ID"""
return db.query(PedimentoConfigUpdateRectification).filter(
PedimentoConfigUpdateRectification.pedimento_id == pedimento_id,
PedimentoConfigUpdateRectification.tenant_id == tenant_id
).first()
@staticmethod
def create(db: Session, config_data: PedimentoConfigUpdateRectificationCreate) -> PedimentoConfigUpdateRectification:
"""Create a new config"""
config = PedimentoConfigUpdateRectification(**config_data.model_dump())
db.add(config)
db.commit()
db.refresh(config)
return config
@staticmethod
def update(
db: Session,
pedimento_id: int,
tenant_id: int,
config_data: PedimentoConfigUpdateRectificationUpdate
) -> Optional[PedimentoConfigUpdateRectification]:
"""Update config"""
config = PedimentoConfigUpdateRectificationService.get_by_pedimento_id(db, pedimento_id, tenant_id)
if not config:
return None
update_data = config_data.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(config, field, value)
db.commit()
db.refresh(config)
return config
@staticmethod
def delete(db: Session, pedimento_id: int, tenant_id: int) -> bool:
"""Delete config"""
config = PedimentoConfigUpdateRectificationService.get_by_pedimento_id(db, pedimento_id, tenant_id)
if not config:
return False
db.delete(config)
db.commit()
return True

View File

@@ -0,0 +1,60 @@
"""
Service layer for PedimentoConfigUpdates CRUD operations
"""
from typing import Optional
from sqlalchemy.orm import Session
from ..models.pedimento_config_updates import PedimentoConfigUpdates
from ..dtos.pedimento_config_updates import PedimentoConfigUpdatesCreate, PedimentoConfigUpdatesUpdate
class PedimentoConfigUpdatesService:
"""Service class for PedimentoConfigUpdates business logic"""
@staticmethod
def get_by_pedimento_id(db: Session, pedimento_id: int, tenant_id: int) -> Optional[PedimentoConfigUpdates]:
"""Get config by pedimento ID"""
return db.query(PedimentoConfigUpdates).filter(
PedimentoConfigUpdates.pedimento_id == pedimento_id,
PedimentoConfigUpdates.tenant_id == tenant_id
).first()
@staticmethod
def create(db: Session, config_data: PedimentoConfigUpdatesCreate) -> PedimentoConfigUpdates:
"""Create a new config"""
config = PedimentoConfigUpdates(**config_data.model_dump())
db.add(config)
db.commit()
db.refresh(config)
return config
@staticmethod
def update(
db: Session,
pedimento_id: int,
tenant_id: int,
config_data: PedimentoConfigUpdatesUpdate
) -> Optional[PedimentoConfigUpdates]:
"""Update config"""
config = PedimentoConfigUpdatesService.get_by_pedimento_id(db, pedimento_id, tenant_id)
if not config:
return None
update_data = config_data.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(config, field, value)
db.commit()
db.refresh(config)
return config
@staticmethod
def delete(db: Session, pedimento_id: int, tenant_id: int) -> bool:
"""Delete config"""
config = PedimentoConfigUpdatesService.get_by_pedimento_id(db, pedimento_id, tenant_id)
if not config:
return False
db.delete(config)
db.commit()
return True

View File

@@ -0,0 +1,60 @@
"""
Service layer for PedimentoCustomsOffices CRUD operations
"""
from typing import Optional
from sqlalchemy.orm import Session
from ..models.pedimento_customs_offices import PedimentoCustomsOffices
from ..dtos.pedimento_customs_offices import PedimentoCustomsOfficesCreate, PedimentoCustomsOfficesUpdate
class PedimentoCustomsOfficesService:
"""Service class for PedimentoCustomsOffices business logic"""
@staticmethod
def get_by_pedimento_id(db: Session, pedimento_id: int, tenant_id: int) -> Optional[PedimentoCustomsOffices]:
"""Get customs offices by pedimento ID"""
return db.query(PedimentoCustomsOffices).filter(
PedimentoCustomsOffices.pedimento_id == pedimento_id,
PedimentoCustomsOffices.tenant_id == tenant_id
).first()
@staticmethod
def create(db: Session, data: PedimentoCustomsOfficesCreate) -> PedimentoCustomsOffices:
"""Create new customs offices"""
offices = PedimentoCustomsOffices(**data.model_dump())
db.add(offices)
db.commit()
db.refresh(offices)
return offices
@staticmethod
def update(
db: Session,
pedimento_id: int,
tenant_id: int,
data: PedimentoCustomsOfficesUpdate
) -> Optional[PedimentoCustomsOffices]:
"""Update customs offices"""
offices = PedimentoCustomsOfficesService.get_by_pedimento_id(db, pedimento_id, tenant_id)
if not offices:
return None
update_data = data.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(offices, field, value)
db.commit()
db.refresh(offices)
return offices
@staticmethod
def delete(db: Session, pedimento_id: int, tenant_id: int) -> bool:
"""Delete customs offices"""
offices = PedimentoCustomsOfficesService.get_by_pedimento_id(db, pedimento_id, tenant_id)
if not offices:
return False
db.delete(offices)
db.commit()
return True

View File

@@ -0,0 +1,60 @@
"""
Service layer for PedimentoDates CRUD operations
"""
from typing import Optional
from sqlalchemy.orm import Session
from ..models.pedimento_dates import PedimentoDates
from ..dtos.pedimento_dates import PedimentoDatesCreate, PedimentoDatesUpdate
class PedimentoDatesService:
"""Service class for PedimentoDates business logic"""
@staticmethod
def get_by_pedimento_id(db: Session, pedimento_id: int, tenant_id: int) -> Optional[PedimentoDates]:
"""Get dates by pedimento ID"""
return db.query(PedimentoDates).filter(
PedimentoDates.pedimento_id == pedimento_id,
PedimentoDates.tenant_id == tenant_id
).first()
@staticmethod
def create(db: Session, data: PedimentoDatesCreate) -> PedimentoDates:
"""Create new pedimento dates"""
dates = PedimentoDates(**data.model_dump())
db.add(dates)
db.commit()
db.refresh(dates)
return dates
@staticmethod
def update(
db: Session,
pedimento_id: int,
tenant_id: int,
data: PedimentoDatesUpdate
) -> Optional[PedimentoDates]:
"""Update pedimento dates"""
dates = PedimentoDatesService.get_by_pedimento_id(db, pedimento_id, tenant_id)
if not dates:
return None
update_data = data.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(dates, field, value)
db.commit()
db.refresh(dates)
return dates
@staticmethod
def delete(db: Session, pedimento_id: int, tenant_id: int) -> bool:
"""Delete pedimento dates"""
dates = PedimentoDatesService.get_by_pedimento_id(db, pedimento_id, tenant_id)
if not dates:
return False
db.delete(dates)
db.commit()
return True

View File

@@ -0,0 +1,60 @@
"""
Service layer for PedimentoDecrementables CRUD operations
"""
from typing import Optional
from sqlalchemy.orm import Session
from ..models.pedimento_decrementables import PedimentoDecrementables
from ..dtos.pedimento_decrementables import PedimentoDecrementablesCreate, PedimentoDecrementablesUpdate
class PedimentoDecrementablesService:
"""Service class for PedimentoDecrementables business logic"""
@staticmethod
def get_by_pedimento_id(db: Session, pedimento_id: int, tenant_id: int) -> Optional[PedimentoDecrementables]:
"""Get decrementables by pedimento ID"""
return db.query(PedimentoDecrementables).filter(
PedimentoDecrementables.pedimento_id == pedimento_id,
PedimentoDecrementables.tenant_id == tenant_id
).first()
@staticmethod
def create(db: Session, data: PedimentoDecrementablesCreate) -> PedimentoDecrementables:
"""Create new decrementables"""
decrementables = PedimentoDecrementables(**data.model_dump())
db.add(decrementables)
db.commit()
db.refresh(decrementables)
return decrementables
@staticmethod
def update(
db: Session,
pedimento_id: int,
tenant_id: int,
data: PedimentoDecrementablesUpdate
) -> Optional[PedimentoDecrementables]:
"""Update decrementables"""
decrementables = PedimentoDecrementablesService.get_by_pedimento_id(db, pedimento_id, tenant_id)
if not decrementables:
return None
update_data = data.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(decrementables, field, value)
db.commit()
db.refresh(decrementables)
return decrementables
@staticmethod
def delete(db: Session, pedimento_id: int, tenant_id: int) -> bool:
"""Delete decrementables"""
decrementables = PedimentoDecrementablesService.get_by_pedimento_id(db, pedimento_id, tenant_id)
if not decrementables:
return False
db.delete(decrementables)
db.commit()
return True

View File

@@ -0,0 +1,60 @@
"""
Service layer for PedimentoIncrementables CRUD operations
"""
from typing import Optional
from sqlalchemy.orm import Session
from ..models.pedimento_incrementables import PedimentoIncrementables
from ..dtos.pedimento_incrementables import PedimentoIncrementablesCreate, PedimentoIncrementablesUpdate
class PedimentoIncrementablesService:
"""Service class for PedimentoIncrementables business logic"""
@staticmethod
def get_by_pedimento_id(db: Session, pedimento_id: int, tenant_id: int) -> Optional[PedimentoIncrementables]:
"""Get incrementables by pedimento ID"""
return db.query(PedimentoIncrementables).filter(
PedimentoIncrementables.pedimento_id == pedimento_id,
PedimentoIncrementables.tenant_id == tenant_id
).first()
@staticmethod
def create(db: Session, data: PedimentoIncrementablesCreate) -> PedimentoIncrementables:
"""Create new incrementables"""
incrementables = PedimentoIncrementables(**data.model_dump())
db.add(incrementables)
db.commit()
db.refresh(incrementables)
return incrementables
@staticmethod
def update(
db: Session,
pedimento_id: int,
tenant_id: int,
data: PedimentoIncrementablesUpdate
) -> Optional[PedimentoIncrementables]:
"""Update incrementables"""
incrementables = PedimentoIncrementablesService.get_by_pedimento_id(db, pedimento_id, tenant_id)
if not incrementables:
return None
update_data = data.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(incrementables, field, value)
db.commit()
db.refresh(incrementables)
return incrementables
@staticmethod
def delete(db: Session, pedimento_id: int, tenant_id: int) -> bool:
"""Delete incrementables"""
incrementables = PedimentoIncrementablesService.get_by_pedimento_id(db, pedimento_id, tenant_id)
if not incrementables:
return False
db.delete(incrementables)
db.commit()
return True

View File

@@ -0,0 +1,60 @@
"""
Service layer for PedimentoIndexes CRUD operations
"""
from typing import Optional
from sqlalchemy.orm import Session
from ..models.pedimento_indexes import PedimentoIndexes
from ..dtos.pedimento_indexes import PedimentoIndexesCreate, PedimentoIndexesUpdate
class PedimentoIndexesService:
"""Service class for PedimentoIndexes business logic"""
@staticmethod
def get_by_pedimento_id(db: Session, pedimento_id: int, tenant_id: int) -> Optional[PedimentoIndexes]:
"""Get indexes by pedimento ID"""
return db.query(PedimentoIndexes).filter(
PedimentoIndexes.pedimento_id == pedimento_id,
PedimentoIndexes.tenant_id == tenant_id
).first()
@staticmethod
def create(db: Session, data: PedimentoIndexesCreate) -> PedimentoIndexes:
"""Create new indexes"""
indexes = PedimentoIndexes(**data.model_dump())
db.add(indexes)
db.commit()
db.refresh(indexes)
return indexes
@staticmethod
def update(
db: Session,
pedimento_id: int,
tenant_id: int,
data: PedimentoIndexesUpdate
) -> Optional[PedimentoIndexes]:
"""Update indexes"""
indexes = PedimentoIndexesService.get_by_pedimento_id(db, pedimento_id, tenant_id)
if not indexes:
return None
update_data = data.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(indexes, field, value)
db.commit()
db.refresh(indexes)
return indexes
@staticmethod
def delete(db: Session, pedimento_id: int, tenant_id: int) -> bool:
"""Delete indexes"""
indexes = PedimentoIndexesService.get_by_pedimento_id(db, pedimento_id, tenant_id)
if not indexes:
return False
db.delete(indexes)
db.commit()
return True

View File

@@ -0,0 +1,60 @@
"""
Service layer for PedimentoPayments CRUD operations
"""
from typing import Optional
from sqlalchemy.orm import Session
from ..models.pedimento_payments import PedimentoPayments
from ..dtos.pedimento_payments import PedimentoPaymentsCreate, PedimentoPaymentsUpdate
class PedimentoPaymentsService:
"""Service class for PedimentoPayments business logic"""
@staticmethod
def get_by_pedimento_id(db: Session, pedimento_id: int, tenant_id: int) -> Optional[PedimentoPayments]:
"""Get payments by pedimento ID"""
return db.query(PedimentoPayments).filter(
PedimentoPayments.pedimento_id == pedimento_id,
PedimentoPayments.tenant_id == tenant_id
).first()
@staticmethod
def create(db: Session, data: PedimentoPaymentsCreate) -> PedimentoPayments:
"""Create new payments"""
payments = PedimentoPayments(**data.model_dump())
db.add(payments)
db.commit()
db.refresh(payments)
return payments
@staticmethod
def update(
db: Session,
pedimento_id: int,
tenant_id: int,
data: PedimentoPaymentsUpdate
) -> Optional[PedimentoPayments]:
"""Update payments"""
payments = PedimentoPaymentsService.get_by_pedimento_id(db, pedimento_id, tenant_id)
if not payments:
return None
update_data = data.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(payments, field, value)
db.commit()
db.refresh(payments)
return payments
@staticmethod
def delete(db: Session, pedimento_id: int, tenant_id: int) -> bool:
"""Delete payments"""
payments = PedimentoPaymentsService.get_by_pedimento_id(db, pedimento_id, tenant_id)
if not payments:
return False
db.delete(payments)
db.commit()
return True

View File

@@ -0,0 +1,60 @@
"""
Service layer for PedimentoRectificationDestination CRUD operations
"""
from typing import Optional
from sqlalchemy.orm import Session
from ..models.pedimento_rectification_destination import PedimentoRectificationDestination
from ..dtos.pedimento_rectification_destination import PedimentoRectificationDestinationCreate, PedimentoRectificationDestinationUpdate
class PedimentoRectificationDestinationService:
"""Service class for PedimentoRectificationDestination business logic"""
@staticmethod
def get_by_pedimento_id(db: Session, pedimento_id: int, tenant_id: int) -> Optional[PedimentoRectificationDestination]:
"""Get rectification destination by pedimento ID"""
return db.query(PedimentoRectificationDestination).filter(
PedimentoRectificationDestination.pedimento_id == pedimento_id,
PedimentoRectificationDestination.tenant_id == tenant_id
).first()
@staticmethod
def create(db: Session, data: PedimentoRectificationDestinationCreate) -> PedimentoRectificationDestination:
"""Create new rectification destination"""
rectification = PedimentoRectificationDestination(**data.model_dump())
db.add(rectification)
db.commit()
db.refresh(rectification)
return rectification
@staticmethod
def update(
db: Session,
pedimento_id: int,
tenant_id: int,
data: PedimentoRectificationDestinationUpdate
) -> Optional[PedimentoRectificationDestination]:
"""Update rectification destination"""
rectification = PedimentoRectificationDestinationService.get_by_pedimento_id(db, pedimento_id, tenant_id)
if not rectification:
return None
update_data = data.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(rectification, field, value)
db.commit()
db.refresh(rectification)
return rectification
@staticmethod
def delete(db: Session, pedimento_id: int, tenant_id: int) -> bool:
"""Delete rectification destination"""
rectification = PedimentoRectificationDestinationService.get_by_pedimento_id(db, pedimento_id, tenant_id)
if not rectification:
return False
db.delete(rectification)
db.commit()
return True

View File

@@ -0,0 +1,60 @@
"""
Service layer for PedimentoRectificationOrigin CRUD operations
"""
from typing import Optional
from sqlalchemy.orm import Session
from ..models.pedimento_rectification_origin import PedimentoRectificationOrigin
from ..dtos.pedimento_rectification_origin import PedimentoRectificationOriginCreate, PedimentoRectificationOriginUpdate
class PedimentoRectificationOriginService:
"""Service class for PedimentoRectificationOrigin business logic"""
@staticmethod
def get_by_pedimento_id(db: Session, pedimento_id: int, tenant_id: int) -> Optional[PedimentoRectificationOrigin]:
"""Get rectification origin by pedimento ID"""
return db.query(PedimentoRectificationOrigin).filter(
PedimentoRectificationOrigin.pedimento_id == pedimento_id,
PedimentoRectificationOrigin.tenant_id == tenant_id
).first()
@staticmethod
def create(db: Session, data: PedimentoRectificationOriginCreate) -> PedimentoRectificationOrigin:
"""Create new rectification origin"""
rectification = PedimentoRectificationOrigin(**data.model_dump())
db.add(rectification)
db.commit()
db.refresh(rectification)
return rectification
@staticmethod
def update(
db: Session,
pedimento_id: int,
tenant_id: int,
data: PedimentoRectificationOriginUpdate
) -> Optional[PedimentoRectificationOrigin]:
"""Update rectification origin"""
rectification = PedimentoRectificationOriginService.get_by_pedimento_id(db, pedimento_id, tenant_id)
if not rectification:
return None
update_data = data.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(rectification, field, value)
db.commit()
db.refresh(rectification)
return rectification
@staticmethod
def delete(db: Session, pedimento_id: int, tenant_id: int) -> bool:
"""Delete rectification origin"""
rectification = PedimentoRectificationOriginService.get_by_pedimento_id(db, pedimento_id, tenant_id)
if not rectification:
return False
db.delete(rectification)
db.commit()
return True

View File

@@ -0,0 +1,60 @@
"""
Service layer for PedimentoTransportMeans CRUD operations
"""
from typing import Optional
from sqlalchemy.orm import Session
from ..models.pedimento_transport_means import PedimentoTransportMeans
from ..dtos.pedimento_transport_means import PedimentoTransportMeansCreate, PedimentoTransportMeansUpdate
class PedimentoTransportMeansService:
"""Service class for PedimentoTransportMeans business logic"""
@staticmethod
def get_by_pedimento_id(db: Session, pedimento_id: int, tenant_id: int) -> Optional[PedimentoTransportMeans]:
"""Get transport means by pedimento ID"""
return db.query(PedimentoTransportMeans).filter(
PedimentoTransportMeans.pedimento_id == pedimento_id,
PedimentoTransportMeans.tenant_id == tenant_id
).first()
@staticmethod
def create(db: Session, data: PedimentoTransportMeansCreate) -> PedimentoTransportMeans:
"""Create new transport means"""
transport = PedimentoTransportMeans(**data.model_dump())
db.add(transport)
db.commit()
db.refresh(transport)
return transport
@staticmethod
def update(
db: Session,
pedimento_id: int,
tenant_id: int,
data: PedimentoTransportMeansUpdate
) -> Optional[PedimentoTransportMeans]:
"""Update transport means"""
transport = PedimentoTransportMeansService.get_by_pedimento_id(db, pedimento_id, tenant_id)
if not transport:
return None
update_data = data.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(transport, field, value)
db.commit()
db.refresh(transport)
return transport
@staticmethod
def delete(db: Session, pedimento_id: int, tenant_id: int) -> bool:
"""Delete transport means"""
transport = PedimentoTransportMeansService.get_by_pedimento_id(db, pedimento_id, tenant_id)
if not transport:
return False
db.delete(transport)
db.commit()
return True

View File

@@ -0,0 +1,60 @@
"""
Service layer for PedimentoValidation CRUD operations
"""
from typing import Optional
from sqlalchemy.orm import Session
from ..models.pedimento_validation import PedimentoValidation
from ..dtos.pedimento_validation import PedimentoValidationCreate, PedimentoValidationUpdate
class PedimentoValidationService:
"""Service class for PedimentoValidation business logic"""
@staticmethod
def get_by_pedimento_id(db: Session, pedimento_id: int, tenant_id: int) -> Optional[PedimentoValidation]:
"""Get validation by pedimento ID"""
return db.query(PedimentoValidation).filter(
PedimentoValidation.pedimento_id == pedimento_id,
PedimentoValidation.tenant_id == tenant_id
).first()
@staticmethod
def create(db: Session, data: PedimentoValidationCreate) -> PedimentoValidation:
"""Create new validation"""
validation = PedimentoValidation(**data.model_dump())
db.add(validation)
db.commit()
db.refresh(validation)
return validation
@staticmethod
def update(
db: Session,
pedimento_id: int,
tenant_id: int,
data: PedimentoValidationUpdate
) -> Optional[PedimentoValidation]:
"""Update validation"""
validation = PedimentoValidationService.get_by_pedimento_id(db, pedimento_id, tenant_id)
if not validation:
return None
update_data = data.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(validation, field, value)
db.commit()
db.refresh(validation)
return validation
@staticmethod
def delete(db: Session, pedimento_id: int, tenant_id: int) -> bool:
"""Delete validation"""
validation = PedimentoValidationService.get_by_pedimento_id(db, pedimento_id, tenant_id)
if not validation:
return False
db.delete(validation)
db.commit()
return True

View File

@@ -0,0 +1,140 @@
"""
Service layer for Pedimentos CRUD operations
"""
from typing import List, Optional, Dict, Any
from sqlalchemy.orm import Session
from sqlalchemy import desc
from fastapi import HTTPException
from ..models.pedimentos import Pedimentos
from ..dtos.pedimentos import PedimentosCreate, PedimentosUpdate
class PedimentosService:
"""Service class for Pedimentos business logic"""
@staticmethod
def get_all(
db: Session,
tenant_id: int,
skip: int = 0,
limit: int = 100,
filters: Optional[Dict[str, Any]] = None
) -> tuple[List[Pedimentos], int]:
"""
Get all pedimentos for a tenant with pagination and filters
Args:
db: Database session
tenant_id: Tenant ID
skip: Number of records to skip
limit: Maximum number of records to return
filters: Optional filters dict
Returns:
Tuple of (list of pedimentos, total count)
"""
query = db.query(Pedimentos).filter(Pedimentos.tenant_id == tenant_id)
if filters:
if filters.get("status"):
query = query.filter(Pedimentos.status == filters["status"])
if filters.get("client_id"):
query = query.filter(Pedimentos.client_id == filters["client_id"])
if filters.get("year"):
query = query.filter(Pedimentos.year == filters["year"])
total = query.count()
items = query.order_by(desc(Pedimentos.created_at)).offset(skip).limit(limit).all()
return items, total
@staticmethod
def get_by_id(db: Session, pedimento_id: int, tenant_id: int) -> Optional[Pedimentos]:
"""
Get a pedimento by ID
Args:
db: Database session
pedimento_id: Pedimento ID
tenant_id: Tenant ID
Returns:
Pedimento or None if not found
"""
return db.query(Pedimentos).filter(
Pedimentos.id == pedimento_id,
Pedimentos.tenant_id == tenant_id
).first()
@staticmethod
def create(db: Session, pedimento_data: PedimentosCreate, tenant_id: int) -> Pedimentos:
"""
Create a new pedimento
Args:
db: Database session
pedimento_data: Pedimento creation data
Returns:
Created pedimento
"""
pedimento = Pedimentos(**pedimento_data.model_dump())
pedimento.tenant_id = 1
db.add(pedimento)
db.commit()
db.refresh(pedimento)
return pedimento
@staticmethod
def update(
db: Session,
pedimento_id: int,
tenant_id: int,
pedimento_data: PedimentosUpdate
) -> Optional[Pedimentos]:
"""
Update a pedimento
Args:
db: Database session
pedimento_id: Pedimento ID
tenant_id: Tenant ID
pedimento_data: Updated data
Returns:
Updated pedimento or None if not found
"""
pedimento = PedimentosService.get_by_id(db, pedimento_id, tenant_id)
if not pedimento:
return None
update_data = pedimento_data.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(pedimento, field, value)
db.commit()
db.refresh(pedimento)
return pedimento
@staticmethod
def delete(db: Session, pedimento_id: int, tenant_id: int) -> bool:
"""
Delete a pedimento
Args:
db: Database session
pedimento_id: Pedimento ID
tenant_id: Tenant ID
Returns:
True if deleted, False if not found
"""
pedimento = PedimentosService.get_by_id(db, pedimento_id, tenant_id)
if not pedimento:
return False
db.delete(pedimento)
db.commit()
return True

View File

@@ -0,0 +1,29 @@
"""
Router principal de API v1
Agrega todos los módulos de la aplicación
"""
from fastapi import APIRouter
# Importar routers de módulos
from .auth import router as auth_router
from .tenants import router as tenants_router
from .licenses import router as licenses_router
from .pedmientos.router import router as pedimentos_router
from .client_and_provider import router as client_and_provider_router
from .company import router as company_router
from .classes import router as classes_router
from .parts import router as parts_router
# Router principal
router = APIRouter()
# Registrar módulos
router.include_router(auth_router)
router.include_router(tenants_router, prefix="/a76", tags=["a76 / tenants"])
router.include_router(licenses_router, prefix="/a76", tags=["a76 / licenses"])
router.include_router(pedimentos_router, prefix="/a76")
router.include_router(client_and_provider_router, prefix="/a76", tags=["a76 / clients and providers"])
router.include_router(company_router, prefix="/a76", tags=["a76 / company"])
router.include_router(classes_router, prefix="/a76", tags=["a76 / classes"])
router.include_router(parts_router, prefix="/a76", tags=["a76 / parts"])

View File

@@ -10,7 +10,7 @@ from core.security import get_current_user, has_role
from .dto import TenantCreateDTO, TenantUpdateDTO, TenantResponseDTO, TenantListResponseDTO
from .service import TenantService
router = APIRouter(prefix="/tenants", tags=["Tenants"])
router = APIRouter(prefix="/tenants")
@router.post("/", response_model=TenantResponseDTO, status_code=201)

View File

@@ -3,7 +3,7 @@ from pydantic import ConfigDict
from typing import Optional
class CodePedimentoRegimenDTO(BaseModel):
id: int
id: Optional[int] = None
pedimento_code: str = Field(..., min_length=1, max_length=3)
regimen_code: Optional[str] = Field(None, min_length=1, max_length=3)
type_code: Optional[str] = Field(None, min_length=1, max_length=1)

View File

@@ -8,7 +8,7 @@ from .dto import CodePedimentoRegimenDTO
from typing import Any, Dict
router = APIRouter(prefix="/code-pedimento-regimens", tags=["Code Pedimento Regimens"])
router = APIRouter(prefix="/code-pedimento-regimens")
@router.get("/", response_model=Dict[str, Any])

View File

@@ -8,7 +8,7 @@ from .dto import ContainerDTO
from typing import Any, Dict
router = APIRouter(prefix="/containers", tags=["Containers"])
router = APIRouter(prefix="/containers")

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