feat: Implement client and provider management service
- Added service layer for handling client and provider operations including creation, retrieval, updating, and deletion. - Introduced DTOs for data transfer and validation. - Implemented filtering and pagination for client/provider listing. - Added logging for better traceability of operations. feat: Create parts management module - Developed a complete module for managing parts/components including creation, retrieval, updating, and deletion. - Introduced DTOs for parts with detailed attributes and validation. - Implemented search and filtering capabilities for parts based on various criteria. - Added endpoints for regulatory information retrieval and parts statistics. - Integrated logging for error handling and operational insights.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -15,7 +15,6 @@ downloads/
|
|||||||
eggs/
|
eggs/
|
||||||
.eggs/
|
.eggs/
|
||||||
lib64/
|
lib64/
|
||||||
parts/
|
|
||||||
sdist/
|
sdist/
|
||||||
var/
|
var/
|
||||||
wheels/
|
wheels/
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import sqlalchemy as sa
|
|||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision: str = '03b786378f94'
|
revision: str = '03b786378f94'
|
||||||
down_revision: Union[str, Sequence[str], None] = '7937209f9718'
|
down_revision: Union[str, Sequence[str], None] = '54f2046774d0'
|
||||||
branch_labels: Union[str, Sequence[str], None] = None
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
depends_on: Union[str, Sequence[str], None] = None
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ def upgrade() -> None:
|
|||||||
"""Upgrade schema - Create only new A76 tables."""
|
"""Upgrade schema - Create only new A76 tables."""
|
||||||
|
|
||||||
# Create new A76 tables only (skip existing tenants, licenses, license_usage)
|
# Create new A76 tables only (skip existing tenants, licenses, license_usage)
|
||||||
op.create_table('gclient_provider',
|
op.create_table('client_provider',
|
||||||
sa.Column('client_id', sa.String(length=8), nullable=False),
|
sa.Column('client_id', sa.String(length=8), nullable=False),
|
||||||
sa.Column('type_nat_foreign', sa.String(length=1), nullable=True),
|
sa.Column('type_nat_foreign', sa.String(length=1), nullable=True),
|
||||||
sa.Column('name', sa.String(length=256), nullable=True),
|
sa.Column('name', sa.String(length=256), nullable=True),
|
||||||
@@ -81,7 +81,7 @@ def upgrade() -> None:
|
|||||||
schema='a76'
|
schema='a76'
|
||||||
)
|
)
|
||||||
|
|
||||||
op.create_table('gclasses',
|
op.create_table('classes',
|
||||||
sa.Column('client_key', sa.Integer(), nullable=False),
|
sa.Column('client_key', sa.Integer(), nullable=False),
|
||||||
sa.Column('class_code', sa.String(length=8), 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_spanish', sa.String(length=500), nullable=True),
|
||||||
@@ -98,7 +98,7 @@ def upgrade() -> None:
|
|||||||
schema='a76'
|
schema='a76'
|
||||||
)
|
)
|
||||||
|
|
||||||
op.create_table('gparts',
|
op.create_table('parts',
|
||||||
sa.Column('client_key', sa.Integer(), nullable=False),
|
sa.Column('client_key', sa.Integer(), nullable=False),
|
||||||
sa.Column('part_number', sa.String(length=49), nullable=False),
|
sa.Column('part_number', sa.String(length=49), nullable=False),
|
||||||
sa.Column('fraction', sa.String(length=10), nullable=True),
|
sa.Column('fraction', sa.String(length=10), nullable=True),
|
||||||
@@ -151,7 +151,7 @@ def upgrade() -> None:
|
|||||||
sa.Column('email', sa.String(length=100), nullable=True),
|
sa.Column('email', sa.String(length=100), nullable=True),
|
||||||
sa.Column('contact', sa.String(length=50), nullable=True),
|
sa.Column('contact', sa.String(length=50), nullable=True),
|
||||||
sa.Column('reference', sa.String(length=250), nullable=True),
|
sa.Column('reference', sa.String(length=250), nullable=True),
|
||||||
sa.ForeignKeyConstraint(['client_id'], ['a76.gclient_provider.client_id'], ondelete='CASCADE'),
|
sa.ForeignKeyConstraint(['client_id'], ['a76.client_provider.client_id'], ondelete='CASCADE'),
|
||||||
sa.PrimaryKeyConstraint('client_id'),
|
sa.PrimaryKeyConstraint('client_id'),
|
||||||
schema='a76'
|
schema='a76'
|
||||||
)
|
)
|
||||||
@@ -178,7 +178,7 @@ def upgrade() -> None:
|
|||||||
sa.Column('subassembly_service', sa.SmallInteger(), nullable=True),
|
sa.Column('subassembly_service', sa.SmallInteger(), nullable=True),
|
||||||
sa.Column('autse_dates', sa.Integer(), nullable=True),
|
sa.Column('autse_dates', sa.Integer(), nullable=True),
|
||||||
sa.Column('autse_number', sa.String(length=300), nullable=True),
|
sa.Column('autse_number', sa.String(length=300), nullable=True),
|
||||||
sa.ForeignKeyConstraint(['client_id'], ['a76.gclient_provider.client_id'], ondelete='CASCADE'),
|
sa.ForeignKeyConstraint(['client_id'], ['a76.client_provider.client_id'], ondelete='CASCADE'),
|
||||||
sa.PrimaryKeyConstraint('client_id'),
|
sa.PrimaryKeyConstraint('client_id'),
|
||||||
schema='a76'
|
schema='a76'
|
||||||
)
|
)
|
||||||
@@ -189,7 +189,7 @@ def downgrade() -> None:
|
|||||||
# Drop tables in reverse dependency order
|
# Drop tables in reverse dependency order
|
||||||
op.drop_table('gclient_provider_programs', schema='a76')
|
op.drop_table('gclient_provider_programs', schema='a76')
|
||||||
op.drop_table('gclient_provider_address', schema='a76')
|
op.drop_table('gclient_provider_address', schema='a76')
|
||||||
op.drop_table('gparts', schema='a76')
|
op.drop_table('parts', schema='a76')
|
||||||
op.drop_table('gclasses', schema='a76')
|
op.drop_table('classes', schema='a76')
|
||||||
op.drop_table('gcompany', schema='a76')
|
op.drop_table('gcompany', schema='a76')
|
||||||
op.drop_table('gclient_provider', schema='a76')
|
op.drop_table('client_provider', schema='a76')
|
||||||
@@ -1,355 +0,0 @@
|
|||||||
"""create_a76_tables_company_clients_parts_classes
|
|
||||||
|
|
||||||
Revision ID: eb8a17e5fbde
|
|
||||||
Revises: 7937209f9718
|
|
||||||
Create Date: 2025-11-06 03:15:17.248159
|
|
||||||
|
|
||||||
"""
|
|
||||||
from typing import Sequence, Union
|
|
||||||
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision: str = 'eb8a17e5fbde'
|
|
||||||
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 new A76 tables only."""
|
|
||||||
|
|
||||||
# Crear tabla gcompany
|
|
||||||
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'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Crear tabla gclient_provider
|
|
||||||
op.create_table('gclient_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'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Crear tabla gclasses
|
|
||||||
op.create_table('gclasses',
|
|
||||||
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'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Crear tabla gparts
|
|
||||||
op.create_table('gparts',
|
|
||||||
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'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Crear tabla gclient_provider_address
|
|
||||||
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.gclient_provider.client_id'], ondelete='CASCADE'),
|
|
||||||
sa.PrimaryKeyConstraint('client_id'),
|
|
||||||
schema='a76'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Crear tabla gclient_provider_programs
|
|
||||||
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.gclient_provider.client_id'], ondelete='CASCADE'),
|
|
||||||
sa.PrimaryKeyConstraint('client_id'),
|
|
||||||
schema='a76'
|
|
||||||
)
|
|
||||||
op.create_table('gclasses',
|
|
||||||
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('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.gclient_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.gclient_provider.client_id'], ondelete='CASCADE'),
|
|
||||||
sa.PrimaryKeyConstraint('client_id'),
|
|
||||||
schema='a76'
|
|
||||||
)
|
|
||||||
op.create_table('gparts',
|
|
||||||
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'
|
|
||||||
)
|
|
||||||
op.create_table('license_usage',
|
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
|
||||||
sa.Column('tenant_id', sa.Integer(), nullable=False),
|
|
||||||
sa.Column('period_start', sa.DateTime(timezone=True), nullable=False),
|
|
||||||
sa.Column('period_end', sa.DateTime(timezone=True), nullable=False),
|
|
||||||
sa.Column('active_users', sa.Integer(), nullable=True),
|
|
||||||
sa.Column('storage_used_gb', sa.Integer(), nullable=True),
|
|
||||||
sa.Column('operations_count', sa.Integer(), nullable=True),
|
|
||||||
sa.Column('api_calls_count', sa.Integer(), 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), server_default=sa.text('now()'), nullable=False),
|
|
||||||
sa.ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], ),
|
|
||||||
sa.PrimaryKeyConstraint('id'),
|
|
||||||
schema='a76'
|
|
||||||
)
|
|
||||||
op.create_index(op.f('ix_a76_license_usage_id'), 'license_usage', ['id'], unique=False, schema='a76')
|
|
||||||
op.create_index(op.f('ix_a76_license_usage_tenant_id'), 'license_usage', ['tenant_id'], unique=False, schema='a76')
|
|
||||||
op.create_table('licenses',
|
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
|
||||||
sa.Column('tenant_id', sa.Integer(), nullable=False),
|
|
||||||
sa.Column('plan', sa.Enum('FREE', 'BASIC', 'PROFESSIONAL', 'ENTERPRISE', name='licenseplan'), nullable=False),
|
|
||||||
sa.Column('status', sa.Enum('ACTIVE', 'EXPIRED', 'SUSPENDED', 'PENDING', 'CANCELLED', name='licensestatus'), nullable=False),
|
|
||||||
sa.Column('max_users', sa.Integer(), nullable=False),
|
|
||||||
sa.Column('max_storage_gb', sa.Integer(), nullable=False),
|
|
||||||
sa.Column('max_monthly_operations', sa.Integer(), nullable=False),
|
|
||||||
sa.Column('feature_api_access', sa.Boolean(), nullable=True),
|
|
||||||
sa.Column('feature_advanced_reports', sa.Boolean(), nullable=True),
|
|
||||||
sa.Column('feature_integrations', sa.Boolean(), nullable=True),
|
|
||||||
sa.Column('feature_dedicated_support', sa.Boolean(), nullable=True),
|
|
||||||
sa.Column('starts_at', sa.DateTime(timezone=True), nullable=False),
|
|
||||||
sa.Column('expires_at', sa.DateTime(timezone=True), nullable=False),
|
|
||||||
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
|
|
||||||
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
|
|
||||||
sa.ForeignKeyConstraint(['tenant_id'], ['a76.tenants.id'], ),
|
|
||||||
sa.PrimaryKeyConstraint('id'),
|
|
||||||
schema='a76'
|
|
||||||
)
|
|
||||||
op.create_index(op.f('ix_a76_licenses_id'), 'licenses', ['id'], unique=False, schema='a76')
|
|
||||||
op.create_index(op.f('ix_a76_licenses_tenant_id'), 'licenses', ['tenant_id'], unique=True, 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_regimenped', 'code_pedimento_regimens', 'pedimento_regimens', ['regimen_code'], ['code'], source_schema='public', referent_schema='public')
|
|
||||||
op.create_foreign_key('fk_codeped', 'code_pedimento_regimens', 'pedimento_codes', ['pedimento_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_codeped', 'code_pedimento_regimens', schema='public', type_='foreignkey')
|
|
||||||
op.drop_constraint('fk_regimenped', '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_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_table('gparts', schema='a76')
|
|
||||||
op.drop_table('gclient_provider_programs', schema='a76')
|
|
||||||
op.drop_table('gclient_provider_address', schema='a76')
|
|
||||||
op.drop_table('gclasses', 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')
|
|
||||||
op.drop_table('gcompany', schema='a76')
|
|
||||||
op.drop_table('gclient_provider', schema='a76')
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
Módulo de GClass
|
Módulo de Class
|
||||||
"""
|
"""
|
||||||
from .routes import router
|
from .routes import router
|
||||||
|
|
||||||
@@ -11,15 +11,15 @@ import enum
|
|||||||
from typing import TYPE_CHECKING, List, Optional
|
from typing import TYPE_CHECKING, List, Optional
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from api.v1.modules.a76.GParts.models import GPart
|
from api.v1.modules.a76.parts.models import Part
|
||||||
from api.v1.modules.public.reference_data.material_types.models import MaterialType
|
from api.v1.modules.public.reference_data.material_types.models import MaterialType
|
||||||
|
|
||||||
|
|
||||||
class GClass(Base):
|
class Class(Base):
|
||||||
"""
|
"""
|
||||||
Modelo para la tabla GClases - Información de clases en sistemas SCAII y SCAF
|
Modelo para la tabla GClases - Información de clases en sistemas SCAII y SCAF
|
||||||
"""
|
"""
|
||||||
__tablename__ = "gclasses"
|
__tablename__ = "classes"
|
||||||
__table_args__ = {"schema": "a76"}
|
__table_args__ = {"schema": "a76"}
|
||||||
|
|
||||||
# Primary key compuesta
|
# Primary key compuesta
|
||||||
@@ -48,14 +48,14 @@ class GClass(Base):
|
|||||||
|
|
||||||
# Inverse relationship with GParts that have this class
|
# Inverse relationship with GParts that have this class
|
||||||
parts = relationship(
|
parts = relationship(
|
||||||
"GPart",
|
"Part",
|
||||||
primaryjoin="and_(GClass.client_key == GPart.client_key, GClass.class_code == GPart.part_class)",
|
primaryjoin="and_(Class.client_key == Part.client_key, Class.class_code == Part.part_class)",
|
||||||
foreign_keys="[GPart.client_key, GPart.part_class]",
|
foreign_keys="[Part.client_key, Part.part_class]",
|
||||||
viewonly=True,
|
viewonly=True,
|
||||||
back_populates="part_class_info"
|
back_populates="part_class_info"
|
||||||
)
|
)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<GClass(client_key={self.client_key}, class_code='{self.class_code}', description='{self.description_spanish}')>"
|
return f"<Class(client_key={self.client_key}, class_code='{self.class_code}', description='{self.description_spanish}')>"
|
||||||
|
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@ from fastapi import HTTPException
|
|||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from .models import GClass
|
from .models import Class
|
||||||
from .dto import (
|
from .dto import (
|
||||||
ClassCreateDTO,
|
ClassCreateDTO,
|
||||||
ClassUpdateDTO,
|
ClassUpdateDTO,
|
||||||
@@ -42,10 +42,10 @@ class ClassService:
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Verificar que no exista la clase
|
# Verificar que no exista la clase
|
||||||
existing = self.db.query(GClass).filter(
|
existing = self.db.query(Class).filter(
|
||||||
and_(
|
and_(
|
||||||
GClass.client_key == class_data.client_key,
|
Class.client_key == class_data.client_key,
|
||||||
GClass.class_code == class_data.class_code
|
Class.class_code == class_data.class_code
|
||||||
)
|
)
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ class ClassService:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Crear clase
|
# Crear clase
|
||||||
db_class = GClass(
|
db_class = Class(
|
||||||
client_key=class_data.client_key,
|
client_key=class_data.client_key,
|
||||||
class_code=class_data.class_code,
|
class_code=class_data.class_code,
|
||||||
description_spanish=class_data.description_spanish,
|
description_spanish=class_data.description_spanish,
|
||||||
@@ -100,10 +100,10 @@ class ClassService:
|
|||||||
Returns:
|
Returns:
|
||||||
ClassResponseDTO o None si no existe
|
ClassResponseDTO o None si no existe
|
||||||
"""
|
"""
|
||||||
class_obj = self.db.query(GClass).filter(
|
class_obj = self.db.query(Class).filter(
|
||||||
and_(
|
and_(
|
||||||
GClass.client_key == client_key,
|
Class.client_key == client_key,
|
||||||
GClass.class_code == class_code
|
Class.class_code == class_code
|
||||||
)
|
)
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
@@ -128,33 +128,33 @@ class ClassService:
|
|||||||
Returns:
|
Returns:
|
||||||
ClassListDTO con la lista paginada
|
ClassListDTO con la lista paginada
|
||||||
"""
|
"""
|
||||||
query = self.db.query(GClass)
|
query = self.db.query(Class)
|
||||||
|
|
||||||
# Aplicar filtros si se proporcionan
|
# Aplicar filtros si se proporcionan
|
||||||
if search_params:
|
if search_params:
|
||||||
if search_params.client_key:
|
if search_params.client_key:
|
||||||
query = query.filter(GClass.client_key == search_params.client_key)
|
query = query.filter(Class.client_key == search_params.client_key)
|
||||||
|
|
||||||
if search_params.class_code:
|
if search_params.class_code:
|
||||||
query = query.filter(GClass.class_code.ilike(f"%{search_params.class_code}%"))
|
query = query.filter(Class.class_code.ilike(f"%{search_params.class_code}%"))
|
||||||
|
|
||||||
if search_params.description:
|
if search_params.description:
|
||||||
description_pattern = f"%{search_params.description}%"
|
description_pattern = f"%{search_params.description}%"
|
||||||
query = query.filter(
|
query = query.filter(
|
||||||
or_(
|
or_(
|
||||||
GClass.description_spanish.ilike(description_pattern),
|
Class.description_spanish.ilike(description_pattern),
|
||||||
GClass.description_english.ilike(description_pattern)
|
Class.description_english.ilike(description_pattern)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if search_params.material_key:
|
if search_params.material_key:
|
||||||
query = query.filter(GClass.material_key.ilike(f"%{search_params.material_key}%"))
|
query = query.filter(Class.material_key.ilike(f"%{search_params.material_key}%"))
|
||||||
|
|
||||||
if search_params.fraction:
|
if search_params.fraction:
|
||||||
query = query.filter(GClass.fraction.ilike(f"%{search_params.fraction}%"))
|
query = query.filter(Class.fraction.ilike(f"%{search_params.fraction}%"))
|
||||||
|
|
||||||
if search_params.physical_review is not None:
|
if search_params.physical_review is not None:
|
||||||
query = query.filter(GClass.physical_review == search_params.physical_review)
|
query = query.filter(Class.physical_review == search_params.physical_review)
|
||||||
|
|
||||||
# Contar total
|
# Contar total
|
||||||
total = query.count()
|
total = query.count()
|
||||||
@@ -184,10 +184,10 @@ class ClassService:
|
|||||||
Returns:
|
Returns:
|
||||||
ClassResponseDTO actualizado o None si no existe
|
ClassResponseDTO actualizado o None si no existe
|
||||||
"""
|
"""
|
||||||
class_obj = self.db.query(GClass).filter(
|
class_obj = self.db.query(Class).filter(
|
||||||
and_(
|
and_(
|
||||||
GClass.client_key == client_key,
|
Class.client_key == client_key,
|
||||||
GClass.class_code == class_code
|
Class.class_code == class_code
|
||||||
)
|
)
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
@@ -222,10 +222,10 @@ class ClassService:
|
|||||||
Returns:
|
Returns:
|
||||||
True si se eliminó, False si no existe
|
True si se eliminó, False si no existe
|
||||||
"""
|
"""
|
||||||
class_obj = self.db.query(GClass).filter(
|
class_obj = self.db.query(Class).filter(
|
||||||
and_(
|
and_(
|
||||||
GClass.client_key == client_key,
|
Class.client_key == client_key,
|
||||||
GClass.class_code == class_code
|
Class.class_code == class_code
|
||||||
)
|
)
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
@@ -244,40 +244,40 @@ class ClassService:
|
|||||||
|
|
||||||
def search_by_fraction(self, fraction: str) -> List[ClassBasicDTO]:
|
def search_by_fraction(self, fraction: str) -> List[ClassBasicDTO]:
|
||||||
"""Busca clases por fracción arancelaria"""
|
"""Busca clases por fracción arancelaria"""
|
||||||
classes = self.db.query(GClass).filter(GClass.fraction.ilike(f"%{fraction}%")).all()
|
classes = self.db.query(Class).filter(Class.fraction.ilike(f"%{fraction}%")).all()
|
||||||
return [ClassBasicDTO.model_validate(class_obj) for class_obj in classes]
|
return [ClassBasicDTO.model_validate(class_obj) for class_obj in classes]
|
||||||
|
|
||||||
def search_by_client(self, client_key: int, skip: int = 0, limit: int = 100) -> List[ClassBasicDTO]:
|
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"""
|
"""Obtiene todas las clases de un cliente específico"""
|
||||||
classes = self.db.query(GClass).filter(GClass.client_key == client_key).offset(skip).limit(limit).all()
|
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]
|
return [ClassBasicDTO.model_validate(class_obj) for class_obj in classes]
|
||||||
|
|
||||||
def search_by_material(self, material_key: str) -> List[ClassBasicDTO]:
|
def search_by_material(self, material_key: str) -> List[ClassBasicDTO]:
|
||||||
"""Busca clases por clave de material"""
|
"""Busca clases por clave de material"""
|
||||||
classes = self.db.query(GClass).filter(GClass.material_key.ilike(f"%{material_key}%")).all()
|
classes = self.db.query(Class).filter(Class.material_key.ilike(f"%{material_key}%")).all()
|
||||||
return [ClassBasicDTO.model_validate(class_obj) for class_obj in classes]
|
return [ClassBasicDTO.model_validate(class_obj) for class_obj in classes]
|
||||||
|
|
||||||
def get_classes_by_physical_review(self, physical_review: int) -> List[ClassBasicDTO]:
|
def get_classes_by_physical_review(self, physical_review: int) -> List[ClassBasicDTO]:
|
||||||
"""Obtiene clases por indicador de revisión física"""
|
"""Obtiene clases por indicador de revisión física"""
|
||||||
classes = self.db.query(GClass).filter(GClass.physical_review == physical_review).all()
|
classes = self.db.query(Class).filter(Class.physical_review == physical_review).all()
|
||||||
return [ClassBasicDTO.model_validate(class_obj) for class_obj in classes]
|
return [ClassBasicDTO.model_validate(class_obj) for class_obj in classes]
|
||||||
|
|
||||||
def get_classes_statistics(self) -> dict:
|
def get_classes_statistics(self) -> dict:
|
||||||
"""Obtiene estadísticas básicas de clases"""
|
"""Obtiene estadísticas básicas de clases"""
|
||||||
total_classes = self.db.query(GClass).count()
|
total_classes = self.db.query(Class).count()
|
||||||
|
|
||||||
# Contar por clientes
|
# Contar por clientes
|
||||||
clients_count = self.db.query(GClass.client_key).distinct().count()
|
clients_count = self.db.query(Class.client_key).distinct().count()
|
||||||
|
|
||||||
# Contar por revisión física
|
# Contar por revisión física
|
||||||
physical_review_stats = {}
|
physical_review_stats = {}
|
||||||
for i in range(3): # Asumiendo valores 0, 1, 2
|
for i in range(3): # Asumiendo valores 0, 1, 2
|
||||||
count = self.db.query(GClass).filter(GClass.physical_review == i).count()
|
count = self.db.query(Class).filter(Class.physical_review == i).count()
|
||||||
physical_review_stats[f"physical_review_{i}"] = count
|
physical_review_stats[f"physical_review_{i}"] = count
|
||||||
|
|
||||||
# Contar clases con fracciones
|
# Contar clases con fracciones
|
||||||
with_fraction = self.db.query(GClass).filter(GClass.fraction.isnot(None)).count()
|
with_fraction = self.db.query(Class).filter(Class.fraction.isnot(None)).count()
|
||||||
with_us_fraction = self.db.query(GClass).filter(GClass.us_fraction.isnot(None)).count()
|
with_us_fraction = self.db.query(Class).filter(Class.us_fraction.isnot(None)).count()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"total_classes": total_classes,
|
"total_classes": total_classes,
|
||||||
@@ -289,6 +289,6 @@ class ClassService:
|
|||||||
|
|
||||||
def get_classes_by_unit_measure(self, unit_of_measure: str) -> List[ClassBasicDTO]:
|
def get_classes_by_unit_measure(self, unit_of_measure: str) -> List[ClassBasicDTO]:
|
||||||
"""Obtiene clases por unidad de medida"""
|
"""Obtiene clases por unidad de medida"""
|
||||||
classes = self.db.query(GClass).filter(GClass.unit_of_measure == unit_of_measure).all()
|
classes = self.db.query(Class).filter(Class.unit_of_measure == unit_of_measure).all()
|
||||||
return [ClassBasicDTO.model_validate(class_obj) for class_obj in classes]
|
return [ClassBasicDTO.model_validate(class_obj) for class_obj in classes]
|
||||||
|
|
||||||
@@ -8,11 +8,11 @@ from core.database import Base
|
|||||||
import enum
|
import enum
|
||||||
|
|
||||||
|
|
||||||
class GClientProvider(Base):
|
class ClientProvider(Base):
|
||||||
"""
|
"""
|
||||||
Modelo para la tabla GClientesPro - Información de clientes y proveedores
|
Modelo para la tabla GClientesPro - Información de clientes y proveedores
|
||||||
"""
|
"""
|
||||||
__tablename__ = "gclient_provider"
|
__tablename__ = "client_provider"
|
||||||
__table_args__ = {"schema": "a76"}
|
__table_args__ = {"schema": "a76"}
|
||||||
|
|
||||||
# Primary key
|
# Primary key
|
||||||
@@ -48,7 +48,7 @@ class GClientProviderAddress(Base):
|
|||||||
__table_args__ = {"schema": "a76"}
|
__table_args__ = {"schema": "a76"}
|
||||||
|
|
||||||
# Primary key (foreign key)
|
# Primary key (foreign key)
|
||||||
client_id = Column(String(8), ForeignKey('a76.gclient_provider.client_id', ondelete='CASCADE'), primary_key=True, nullable=False)
|
client_id = Column(String(8), ForeignKey('a76.client_provider.client_id', ondelete='CASCADE'), primary_key=True, nullable=False)
|
||||||
|
|
||||||
# Address information
|
# Address information
|
||||||
municipality = Column(String(150), nullable=True)
|
municipality = Column(String(150), nullable=True)
|
||||||
@@ -67,7 +67,7 @@ class GClientProviderAddress(Base):
|
|||||||
reference = Column(String(250), nullable=True)
|
reference = Column(String(250), nullable=True)
|
||||||
|
|
||||||
# Relationship
|
# Relationship
|
||||||
client_provider = relationship("GClientProvider", back_populates="address")
|
client_provider = relationship("ClientProvider", back_populates="address")
|
||||||
|
|
||||||
|
|
||||||
class GClientProviderPrograms(Base):
|
class GClientProviderPrograms(Base):
|
||||||
@@ -78,7 +78,7 @@ class GClientProviderPrograms(Base):
|
|||||||
__table_args__ = {"schema": "a76"}
|
__table_args__ = {"schema": "a76"}
|
||||||
|
|
||||||
# Primary key (foreign key)
|
# Primary key (foreign key)
|
||||||
client_id = Column(String(8), ForeignKey('a76.gclient_provider.client_id', ondelete='CASCADE'), primary_key=True, nullable=False)
|
client_id = Column(String(8), ForeignKey('a76.client_provider.client_id', ondelete='CASCADE'), primary_key=True, nullable=False)
|
||||||
|
|
||||||
# Program information
|
# Program information
|
||||||
program = Column(String(7), nullable=True)
|
program = Column(String(7), nullable=True)
|
||||||
@@ -103,6 +103,6 @@ class GClientProviderPrograms(Base):
|
|||||||
autse_number = Column(String(300), nullable=True)
|
autse_number = Column(String(300), nullable=True)
|
||||||
|
|
||||||
# Relationship
|
# Relationship
|
||||||
client_provider = relationship("GClientProvider", back_populates="programs")
|
client_provider = relationship("ClientProvider", back_populates="programs")
|
||||||
|
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ from .dto import (
|
|||||||
ClientProviderListDTO
|
ClientProviderListDTO
|
||||||
)
|
)
|
||||||
|
|
||||||
router = APIRouter(prefix="/clients-providers", tags=["Clients & Providers"])
|
router = APIRouter(prefix="/clients-providers")
|
||||||
|
|
||||||
|
|
||||||
@router.post("/", response_model=ClientProviderResponseDTO, status_code=status.HTTP_201_CREATED)
|
@router.post("/", response_model=ClientProviderResponseDTO, status_code=status.HTTP_201_CREATED)
|
||||||
@@ -8,7 +8,7 @@ from fastapi import HTTPException
|
|||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from .models import GClientProvider, GClientProviderAddress, GClientProviderPrograms
|
from .models import ClientProvider, GClientProviderAddress, GClientProviderPrograms
|
||||||
from .dto import (
|
from .dto import (
|
||||||
ClientProviderCreateDTO,
|
ClientProviderCreateDTO,
|
||||||
ClientProviderUpdateDTO,
|
ClientProviderUpdateDTO,
|
||||||
@@ -43,12 +43,12 @@ class ClientProviderService:
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Verificar que no exista el cliente
|
# Verificar que no exista el cliente
|
||||||
existing = self.db.query(GClientProvider).filter(GClientProvider.client_id == client_data.client_id).first()
|
existing = self.db.query(ClientProvider).filter(ClientProvider.client_id == client_data.client_id).first()
|
||||||
if existing:
|
if existing:
|
||||||
raise HTTPException(status_code=400, detail=f"Client with ID '{client_data.client_id}' already exists")
|
raise HTTPException(status_code=400, detail=f"Client with ID '{client_data.client_id}' already exists")
|
||||||
|
|
||||||
# Crear cliente/proveedor principal
|
# Crear cliente/proveedor principal
|
||||||
db_client = GClientProvider(
|
db_client = ClientProvider(
|
||||||
client_id=client_data.client_id,
|
client_id=client_data.client_id,
|
||||||
type_nat_foreign=client_data.type_nat_foreign,
|
type_nat_foreign=client_data.type_nat_foreign,
|
||||||
name=client_data.name,
|
name=client_data.name,
|
||||||
@@ -118,10 +118,10 @@ class ClientProviderService:
|
|||||||
|
|
||||||
def _get_client_with_relations(self, client_id: str) -> Optional[ClientProviderResponseDTO]:
|
def _get_client_with_relations(self, client_id: str) -> Optional[ClientProviderResponseDTO]:
|
||||||
"""Método privado para obtener cliente con relaciones"""
|
"""Método privado para obtener cliente con relaciones"""
|
||||||
client = self.db.query(GClientProvider).options(
|
client = self.db.query(ClientProvider).options(
|
||||||
joinedload(GClientProvider.address),
|
joinedload(ClientProvider.address),
|
||||||
joinedload(GClientProvider.programs)
|
joinedload(ClientProvider.programs)
|
||||||
).filter(GClientProvider.client_id == client_id).first()
|
).filter(ClientProvider.client_id == client_id).first()
|
||||||
|
|
||||||
if not client:
|
if not client:
|
||||||
return None
|
return None
|
||||||
@@ -148,25 +148,25 @@ class ClientProviderService:
|
|||||||
Returns:
|
Returns:
|
||||||
ClientProviderListDTO con la lista paginada
|
ClientProviderListDTO con la lista paginada
|
||||||
"""
|
"""
|
||||||
query = self.db.query(GClientProvider)
|
query = self.db.query(ClientProvider)
|
||||||
|
|
||||||
# Aplicar filtros
|
# Aplicar filtros
|
||||||
if search:
|
if search:
|
||||||
search_pattern = f"%{search}%"
|
search_pattern = f"%{search}%"
|
||||||
query = query.filter(
|
query = query.filter(
|
||||||
or_(
|
or_(
|
||||||
GClientProvider.name.ilike(search_pattern),
|
ClientProvider.name.ilike(search_pattern),
|
||||||
GClientProvider.short_name.ilike(search_pattern),
|
ClientProvider.short_name.ilike(search_pattern),
|
||||||
GClientProvider.rfc.ilike(search_pattern),
|
ClientProvider.rfc.ilike(search_pattern),
|
||||||
GClientProvider.client_id.ilike(search_pattern)
|
ClientProvider.client_id.ilike(search_pattern)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if client_or_provider:
|
if client_or_provider:
|
||||||
query = query.filter(GClientProvider.client_or_provider == client_or_provider)
|
query = query.filter(ClientProvider.client_or_provider == client_or_provider)
|
||||||
|
|
||||||
if enabled_only:
|
if enabled_only:
|
||||||
query = query.filter(GClientProvider.enabled_disabled == 1)
|
query = query.filter(ClientProvider.enabled_disabled == 1)
|
||||||
|
|
||||||
# Contar total
|
# Contar total
|
||||||
total = query.count()
|
total = query.count()
|
||||||
@@ -195,7 +195,7 @@ class ClientProviderService:
|
|||||||
Returns:
|
Returns:
|
||||||
ClientProviderResponseDTO actualizado o None si no existe
|
ClientProviderResponseDTO actualizado o None si no existe
|
||||||
"""
|
"""
|
||||||
client = self.db.query(GClientProvider).filter(GClientProvider.client_id == client_id).first()
|
client = self.db.query(ClientProvider).filter(ClientProvider.client_id == client_id).first()
|
||||||
if not client:
|
if not client:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -257,7 +257,7 @@ class ClientProviderService:
|
|||||||
Returns:
|
Returns:
|
||||||
True si se eliminó, False si no existe
|
True si se eliminó, False si no existe
|
||||||
"""
|
"""
|
||||||
client = self.db.query(GClientProvider).filter(GClientProvider.client_id == client_id).first()
|
client = self.db.query(ClientProvider).filter(ClientProvider.client_id == client_id).first()
|
||||||
if not client:
|
if not client:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -273,24 +273,24 @@ class ClientProviderService:
|
|||||||
|
|
||||||
def get_clients_only(self, skip: int = 0, limit: int = 100) -> List[ClientProviderBasicDTO]:
|
def get_clients_only(self, skip: int = 0, limit: int = 100) -> List[ClientProviderBasicDTO]:
|
||||||
"""Obtiene solo clientes (C)"""
|
"""Obtiene solo clientes (C)"""
|
||||||
query = self.db.query(GClientProvider).filter(GClientProvider.client_or_provider == 'C')
|
query = self.db.query(ClientProvider).filter(ClientProvider.client_or_provider == 'C')
|
||||||
clients = query.offset(skip).limit(limit).all()
|
clients = query.offset(skip).limit(limit).all()
|
||||||
return [ClientProviderBasicDTO.model_validate(client) for client in clients]
|
return [ClientProviderBasicDTO.model_validate(client) for client in clients]
|
||||||
|
|
||||||
def get_providers_only(self, skip: int = 0, limit: int = 100) -> List[ClientProviderBasicDTO]:
|
def get_providers_only(self, skip: int = 0, limit: int = 100) -> List[ClientProviderBasicDTO]:
|
||||||
"""Obtiene solo proveedores (P)"""
|
"""Obtiene solo proveedores (P)"""
|
||||||
query = self.db.query(GClientProvider).filter(GClientProvider.client_or_provider == 'P')
|
query = self.db.query(ClientProvider).filter(ClientProvider.client_or_provider == 'P')
|
||||||
providers = query.offset(skip).limit(limit).all()
|
providers = query.offset(skip).limit(limit).all()
|
||||||
return [ClientProviderBasicDTO.model_validate(provider) for provider in providers]
|
return [ClientProviderBasicDTO.model_validate(provider) for provider in providers]
|
||||||
|
|
||||||
def search_by_rfc(self, rfc: str) -> List[ClientProviderBasicDTO]:
|
def search_by_rfc(self, rfc: str) -> List[ClientProviderBasicDTO]:
|
||||||
"""Busca clientes/proveedores por RFC"""
|
"""Busca clientes/proveedores por RFC"""
|
||||||
clients = self.db.query(GClientProvider).filter(GClientProvider.rfc.ilike(f"%{rfc}%")).all()
|
clients = self.db.query(ClientProvider).filter(ClientProvider.rfc.ilike(f"%{rfc}%")).all()
|
||||||
return [ClientProviderBasicDTO.model_validate(client) for client in clients]
|
return [ClientProviderBasicDTO.model_validate(client) for client in clients]
|
||||||
|
|
||||||
def toggle_status(self, client_id: str) -> Optional[ClientProviderResponseDTO]:
|
def toggle_status(self, client_id: str) -> Optional[ClientProviderResponseDTO]:
|
||||||
"""Cambia el estado habilitado/deshabilitado"""
|
"""Cambia el estado habilitado/deshabilitado"""
|
||||||
client = self.db.query(GClientProvider).filter(GClientProvider.client_id == client_id).first()
|
client = self.db.query(ClientProvider).filter(ClientProvider.client_id == client_id).first()
|
||||||
if not client:
|
if not client:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ from core.security import get_current_user, has_role
|
|||||||
from .service import CompanyService
|
from .service import CompanyService
|
||||||
from .dto import CompanyCreateDTO, CompanyUpdateDTO, CompanyResponseDTO
|
from .dto import CompanyCreateDTO, CompanyUpdateDTO, CompanyResponseDTO
|
||||||
|
|
||||||
router = APIRouter(prefix="/company", tags=["Company"])
|
router = APIRouter(prefix="/company")
|
||||||
|
|
||||||
|
|
||||||
@router.post("/", response_model=CompanyResponseDTO, status_code=status.HTTP_201_CREATED)
|
@router.post("/", response_model=CompanyResponseDTO, status_code=status.HTTP_201_CREATED)
|
||||||
|
|||||||
@@ -13,14 +13,14 @@ from typing import TYPE_CHECKING, Optional
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from api.v1.modules.public.reference_data.countries.models import Country
|
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.public.reference_data.currency_types.models import CurrencyType
|
||||||
from api.v1.modules.a76.GClass.models import GClass
|
from api.v1.modules.a76.classes.models import Class
|
||||||
|
|
||||||
|
|
||||||
class GPart(Base):
|
class Part(Base):
|
||||||
"""
|
"""
|
||||||
Modelo para la tabla GPartes - Información de partes en los sistemas SCAII (N), SCAF (S) Y WINSAAI (W)
|
Modelo para la tabla GPartes - Información de partes en los sistemas SCAII (N), SCAF (S) Y WINSAAI (W)
|
||||||
"""
|
"""
|
||||||
__tablename__ = "gparts"
|
__tablename__ = "parts"
|
||||||
__table_args__ = {"schema": "a76"}
|
__table_args__ = {"schema": "a76"}
|
||||||
|
|
||||||
# Primary key compuesta
|
# Primary key compuesta
|
||||||
@@ -72,17 +72,17 @@ class GPart(Base):
|
|||||||
country = relationship("Country", foreign_keys=[country_of_origin])
|
country = relationship("Country", foreign_keys=[country_of_origin])
|
||||||
currency = relationship("CurrencyType", foreign_keys=[currency_key])
|
currency = relationship("CurrencyType", foreign_keys=[currency_key])
|
||||||
|
|
||||||
# Relationship with GClass through composite foreign 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 GClass
|
# Note: This requires both client_key and part_class to match client_key and class_code in Class
|
||||||
part_class_info = relationship(
|
part_class_info = relationship(
|
||||||
"GClass",
|
"Class",
|
||||||
primaryjoin="and_(GPart.client_key == GClass.client_key, GPart.part_class == GClass.class_code)",
|
primaryjoin="and_(Part.client_key == Class.client_key, Part.part_class == Class.class_code)",
|
||||||
foreign_keys="[GPart.client_key, GPart.part_class]",
|
foreign_keys="[Part.client_key, Part.part_class]",
|
||||||
viewonly=True,
|
viewonly=True,
|
||||||
back_populates="parts"
|
back_populates="parts"
|
||||||
)
|
)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<GPart(client_key={self.client_key}, part_number='{self.part_number}', description='{self.description_spanish}')>"
|
return f"<Part(client_key={self.client_key}, part_number='{self.part_number}', description='{self.description_spanish}')>"
|
||||||
|
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ from .dto import (
|
|||||||
PartSearchDTO
|
PartSearchDTO
|
||||||
)
|
)
|
||||||
|
|
||||||
router = APIRouter(prefix="/parts", tags=["Parts"])
|
router = APIRouter(prefix="/parts")
|
||||||
|
|
||||||
|
|
||||||
@router.post("/", response_model=PartResponseDTO, status_code=status.HTTP_201_CREATED)
|
@router.post("/", response_model=PartResponseDTO, status_code=status.HTTP_201_CREATED)
|
||||||
@@ -9,7 +9,7 @@ from typing import List, Optional
|
|||||||
import logging
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from .models import GPart
|
from .models import Part
|
||||||
from .dto import PartCreateDTO, PartUpdateDTO
|
from .dto import PartCreateDTO, PartUpdateDTO
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -21,12 +21,12 @@ class PartService:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_part(db: Session, part_data: PartCreateDTO) -> GPart:
|
def create_part(db: Session, part_data: PartCreateDTO) -> Part:
|
||||||
"""
|
"""
|
||||||
Crear una nueva parte
|
Crear una nueva parte
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
db_part = GPart(**part_data.model_dump())
|
db_part = Part(**part_data.model_dump())
|
||||||
db.add(db_part)
|
db.add(db_part)
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(db_part)
|
db.refresh(db_part)
|
||||||
@@ -41,15 +41,15 @@ class PartService:
|
|||||||
raise HTTPException(status_code=500, detail="Error creating part")
|
raise HTTPException(status_code=500, detail="Error creating part")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_part(db: Session, client_key: int, part_number: str) -> Optional[GPart]:
|
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
|
Obtener una parte por clave de cliente y número de parte
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return db.query(GPart).filter(
|
return db.query(Part).filter(
|
||||||
and_(
|
and_(
|
||||||
GPart.client_key == client_key,
|
Part.client_key == client_key,
|
||||||
GPart.part_number == part_number
|
Part.part_number == part_number
|
||||||
)
|
)
|
||||||
).first()
|
).first()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -65,29 +65,29 @@ class PartService:
|
|||||||
client_key: Optional[int] = None,
|
client_key: Optional[int] = None,
|
||||||
fraction: Optional[str] = None,
|
fraction: Optional[str] = None,
|
||||||
country_of_origin: Optional[str] = None
|
country_of_origin: Optional[str] = None
|
||||||
) -> tuple[List[GPart], int]:
|
) -> tuple[List[Part], int]:
|
||||||
"""
|
"""
|
||||||
Obtener partes con paginación y filtros
|
Obtener partes con paginación y filtros
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
query = db.query(GPart)
|
query = db.query(Part)
|
||||||
|
|
||||||
# Aplicar filtros
|
# Aplicar filtros
|
||||||
if search:
|
if search:
|
||||||
query = query.filter(or_(
|
query = query.filter(or_(
|
||||||
GPart.description_spanish.ilike(f"%{search}%"),
|
Part.description_spanish.ilike(f"%{search}%"),
|
||||||
GPart.description_english.ilike(f"%{search}%"),
|
Part.description_english.ilike(f"%{search}%"),
|
||||||
GPart.part_number.ilike(f"%{search}%")
|
Part.part_number.ilike(f"%{search}%")
|
||||||
))
|
))
|
||||||
|
|
||||||
if client_key is not None:
|
if client_key is not None:
|
||||||
query = query.filter(GPart.client_key == client_key)
|
query = query.filter(Part.client_key == client_key)
|
||||||
|
|
||||||
if fraction:
|
if fraction:
|
||||||
query = query.filter(GPart.fraction == fraction)
|
query = query.filter(Part.fraction == fraction)
|
||||||
|
|
||||||
if country_of_origin:
|
if country_of_origin:
|
||||||
query = query.filter(GPart.country_of_origin == country_of_origin)
|
query = query.filter(Part.country_of_origin == country_of_origin)
|
||||||
|
|
||||||
# Contar total
|
# Contar total
|
||||||
total = query.count()
|
total = query.count()
|
||||||
@@ -101,26 +101,26 @@ class PartService:
|
|||||||
raise HTTPException(status_code=500, detail="Error retrieving parts")
|
raise HTTPException(status_code=500, detail="Error retrieving parts")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_parts_by_client(db: Session, client_key: int) -> List[GPart]:
|
def get_parts_by_client(db: Session, client_key: int) -> List[Part]:
|
||||||
"""
|
"""
|
||||||
Obtener todas las partes de un cliente específico
|
Obtener todas las partes de un cliente específico
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return db.query(GPart).filter(GPart.client_key == client_key).all()
|
return db.query(Part).filter(Part.client_key == client_key).all()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error getting parts by client: {e}")
|
logger.error(f"Error getting parts by client: {e}")
|
||||||
raise HTTPException(status_code=500, detail="Error retrieving client parts")
|
raise HTTPException(status_code=500, detail="Error retrieving client parts")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def search_parts_by_fraction(db: Session, fraction: str) -> List[GPart]:
|
def search_parts_by_fraction(db: Session, fraction: str) -> List[Part]:
|
||||||
"""
|
"""
|
||||||
Buscar partes por fracción arancelaria
|
Buscar partes por fracción arancelaria
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return db.query(GPart).filter(
|
return db.query(Part).filter(
|
||||||
or_(
|
or_(
|
||||||
GPart.fraction.ilike(f"%{fraction}%"),
|
Part.fraction.ilike(f"%{fraction}%"),
|
||||||
GPart.us_fraction.ilike(f"%{fraction}%")
|
Part.us_fraction.ilike(f"%{fraction}%")
|
||||||
)
|
)
|
||||||
).all()
|
).all()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -128,29 +128,29 @@ class PartService:
|
|||||||
raise HTTPException(status_code=500, detail="Error searching parts by fraction")
|
raise HTTPException(status_code=500, detail="Error searching parts by fraction")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def search_parts_by_supplier(db: Session, supplier: str) -> List[GPart]:
|
def search_parts_by_supplier(db: Session, supplier: str) -> List[Part]:
|
||||||
"""
|
"""
|
||||||
Buscar partes por proveedor
|
Buscar partes por proveedor
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return db.query(GPart).filter(GPart.supplier.ilike(f"%{supplier}%")).all()
|
return db.query(Part).filter(Part.supplier.ilike(f"%{supplier}%")).all()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error searching parts by supplier: {e}")
|
logger.error(f"Error searching parts by supplier: {e}")
|
||||||
raise HTTPException(status_code=500, detail="Error searching parts by supplier")
|
raise HTTPException(status_code=500, detail="Error searching parts by supplier")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def search_parts_by_country(db: Session, country_code: str) -> List[GPart]:
|
def search_parts_by_country(db: Session, country_code: str) -> List[Part]:
|
||||||
"""
|
"""
|
||||||
Buscar partes por país de origen
|
Buscar partes por país de origen
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return db.query(GPart).filter(GPart.country_of_origin == country_code).all()
|
return db.query(Part).filter(Part.country_of_origin == country_code).all()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error searching parts by country: {e}")
|
logger.error(f"Error searching parts by country: {e}")
|
||||||
raise HTTPException(status_code=500, detail="Error searching parts by country")
|
raise HTTPException(status_code=500, detail="Error searching parts by country")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def update_part(db: Session, client_key: int, part_number: str, part_data: PartUpdateDTO) -> Optional[GPart]:
|
def update_part(db: Session, client_key: int, part_number: str, part_data: PartUpdateDTO) -> Optional[Part]:
|
||||||
"""
|
"""
|
||||||
Actualizar una parte existente
|
Actualizar una parte existente
|
||||||
"""
|
"""
|
||||||
@@ -190,7 +190,7 @@ class PartService:
|
|||||||
raise HTTPException(status_code=500, detail="Error deleting part")
|
raise HTTPException(status_code=500, detail="Error deleting part")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def toggle_part_status(db: Session, client_key: int, part_number: str) -> Optional[GPart]:
|
def toggle_part_status(db: Session, client_key: int, part_number: str) -> Optional[Part]:
|
||||||
"""
|
"""
|
||||||
Cambiar el estado habilitado/deshabilitado de una parte
|
Cambiar el estado habilitado/deshabilitado de una parte
|
||||||
"""
|
"""
|
||||||
@@ -216,24 +216,24 @@ class PartService:
|
|||||||
Obtener estadísticas de partes
|
Obtener estadísticas de partes
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
total_parts = db.query(GPart).count()
|
total_parts = db.query(Part).count()
|
||||||
|
|
||||||
# Partes por cliente
|
# Partes por cliente
|
||||||
parts_by_client = db.query(
|
parts_by_client = db.query(
|
||||||
GPart.client_key,
|
Part.client_key,
|
||||||
func.count(GPart.part_number).label('count')
|
func.count(Part.part_number).label('count')
|
||||||
).group_by(GPart.client_key).all()
|
).group_by(Part.client_key).all()
|
||||||
|
|
||||||
# Partes por país de origen
|
# Partes por país de origen
|
||||||
parts_by_country = db.query(
|
parts_by_country = db.query(
|
||||||
GPart.country_of_origin,
|
Part.country_of_origin,
|
||||||
func.count(GPart.part_number).label('count')
|
func.count(Part.part_number).label('count')
|
||||||
).filter(GPart.country_of_origin.isnot(None))\
|
).filter(Part.country_of_origin.isnot(None))\
|
||||||
.group_by(GPart.country_of_origin).all()
|
.group_by(Part.country_of_origin).all()
|
||||||
|
|
||||||
# Partes habilitadas vs deshabilitadas
|
# Partes habilitadas vs deshabilitadas
|
||||||
enabled_parts = db.query(GPart).filter(GPart.enabled_disabled == 1).count()
|
enabled_parts = db.query(Part).filter(Part.enabled_disabled == 1).count()
|
||||||
disabled_parts = db.query(GPart).filter(GPart.enabled_disabled == 0).count()
|
disabled_parts = db.query(Part).filter(Part.enabled_disabled == 0).count()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"total_parts": total_parts,
|
"total_parts": total_parts,
|
||||||
@@ -9,6 +9,10 @@ from .auth import router as auth_router
|
|||||||
from .tenants import router as tenants_router
|
from .tenants import router as tenants_router
|
||||||
from .licenses import router as licenses_router
|
from .licenses import router as licenses_router
|
||||||
from .pedmientos.router import router as pedimentos_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 principal
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
@@ -18,3 +22,8 @@ router.include_router(auth_router)
|
|||||||
router.include_router(tenants_router, prefix="/a76", tags=["a76 / tenants"])
|
router.include_router(tenants_router, prefix="/a76", tags=["a76 / tenants"])
|
||||||
router.include_router(licenses_router, prefix="/a76", tags=["a76 / licenses"])
|
router.include_router(licenses_router, prefix="/a76", tags=["a76 / licenses"])
|
||||||
router.include_router(pedimentos_router, prefix="/a76")
|
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"])
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
- Control de inventario y clasificación arancelaria
|
- Control de inventario y clasificación arancelaria
|
||||||
- Información regulatoria y de cumplimiento
|
- Información regulatoria y de cumplimiento
|
||||||
|
|
||||||
### Módulo de Clases (GClass)
|
### Módulo de Clases (Class)
|
||||||
- Clasificaciones para sistemas SCAII y SCAF
|
- Clasificaciones para sistemas SCAII y SCAF
|
||||||
- Información arancelaria detallada
|
- Información arancelaria detallada
|
||||||
- Gestión de fracciones arancelarias y materiales
|
- Gestión de fracciones arancelarias y materiales
|
||||||
@@ -30,17 +30,17 @@
|
|||||||
## 🔗 Relaciones de Base de Datos
|
## 🔗 Relaciones de Base de Datos
|
||||||
|
|
||||||
### Relaciones Principales
|
### Relaciones Principales
|
||||||
- **GPart ↔ GClass**: Relación de clave compuesta (client_key, part_class ↔ class_code)
|
- **Part ↔ Class**: Relación de clave compuesta (client_key, part_class ↔ class_code)
|
||||||
- **GPart → Country**: Clave foránea a public.countries (country_of_origin)
|
- **Part → Country**: Clave foránea a public.countries (country_of_origin)
|
||||||
- **GPart → CurrencyType**: Clave foránea a public.currency_types (currency_key)
|
- **Part → CurrencyType**: Clave foránea a public.currency_types (currency_key)
|
||||||
- **GClass → MaterialType**: Clave foránea a public.material_types (material_key)
|
- **Class → MaterialType**: Clave foránea a public.material_types (material_key)
|
||||||
|
|
||||||
### Esquema de Relaciones
|
### Esquema de Relaciones
|
||||||
```
|
```
|
||||||
GPart (Partes)
|
Part (Partes)
|
||||||
├── País de origen → Country
|
├── País de origen → Country
|
||||||
├── Tipo de moneda → CurrencyType
|
├── Tipo de moneda → CurrencyType
|
||||||
└── Información de clase → GClass
|
└── Información de clase → Class
|
||||||
└── Tipo de material → MaterialType
|
└── Tipo de material → MaterialType
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
## Resumen de Relaciones Establecidas
|
## Resumen de Relaciones Establecidas
|
||||||
|
|
||||||
### GPart (Tabla: gparts)
|
### Part (Tabla: parts)
|
||||||
El modelo `GPart` representa las partes/componentes en los sistemas SCAII, SCAF y WINSAAI.
|
El modelo `Part` representa las partes/componentes en los sistemas SCAII, SCAF y WINSAAI.
|
||||||
|
|
||||||
#### Relaciones:
|
#### Relaciones:
|
||||||
|
|
||||||
@@ -17,14 +17,14 @@ El modelo `GPart` representa las partes/componentes en los sistemas SCAII, SCAF
|
|||||||
- Relación: Many-to-One
|
- Relación: Many-to-One
|
||||||
- Propósito: Tipo de moneda para el costo unitario
|
- Propósito: Tipo de moneda para el costo unitario
|
||||||
|
|
||||||
3. **Con GClass (gclasses)**
|
3. **Con Class (classes)**
|
||||||
- Campos: `(client_key, part_class)` → `(client_key, class_code)`
|
- Campos: `(client_key, part_class)` → `(client_key, class_code)`
|
||||||
- Relación: Many-to-One (usando primaryjoin complejo)
|
- Relación: Many-to-One (usando primaryjoin complejo)
|
||||||
- Propósito: Clasificación de la parte
|
- Propósito: Clasificación de la parte
|
||||||
- Atributo: `part_class_info`
|
- Atributo: `part_class_info`
|
||||||
|
|
||||||
### GClass (Tabla: gclasses)
|
### Class (Tabla: classes)
|
||||||
El modelo `GClass` representa las clases de clasificación en sistemas SCAII y SCAF.
|
El modelo `Class` representa las clases de clasificación en sistemas SCAII y SCAF.
|
||||||
|
|
||||||
#### Relaciones:
|
#### Relaciones:
|
||||||
|
|
||||||
@@ -33,24 +33,24 @@ El modelo `GClass` representa las clases de clasificación en sistemas SCAII y S
|
|||||||
- Relación: Many-to-One
|
- Relación: Many-to-One
|
||||||
- Propósito: Tipo de material de la clase
|
- Propósito: Tipo de material de la clase
|
||||||
|
|
||||||
2. **Con GPart (gparts)**
|
2. **Con Part (parts)**
|
||||||
- Campos: `(client_key, class_code)` → `(client_key, part_class)`
|
- Campos: `(client_key, class_code)` → `(client_key, part_class)`
|
||||||
- Relación: One-to-Many (inversa de la relación en GPart)
|
- Relación: One-to-Many (inversa de la relación en Part)
|
||||||
- Propósito: Partes que pertenecen a esta clase
|
- Propósito: Partes que pertenecen a esta clase
|
||||||
- Atributo: `parts`
|
- Atributo: `parts`
|
||||||
|
|
||||||
## Esquema de Relaciones
|
## Esquema de Relaciones
|
||||||
|
|
||||||
```
|
```
|
||||||
GPart
|
Part
|
||||||
├── country (Country) # País de origen
|
├── country (Country) # País de origen
|
||||||
├── currency (CurrencyType) # Tipo de moneda
|
├── currency (CurrencyType) # Tipo de moneda
|
||||||
└── part_class_info (GClass) # Información de clasificación
|
└── part_class_info (Class) # Información de clasificación
|
||||||
└── material_type (MaterialType) # Tipo de material
|
└── material_type (MaterialType) # Tipo de material
|
||||||
|
|
||||||
GClass
|
Class
|
||||||
├── material_type (MaterialType) # Tipo de material
|
├── material_type (MaterialType) # Tipo de material
|
||||||
└── parts (List[GPart]) # Partes que usan esta clase
|
└── parts (List[Part]) # Partes que usan esta clase
|
||||||
```
|
```
|
||||||
|
|
||||||
## Uso de las Relaciones
|
## Uso de las Relaciones
|
||||||
@@ -58,13 +58,13 @@ GClass
|
|||||||
### En consultas:
|
### En consultas:
|
||||||
```python
|
```python
|
||||||
# Obtener una parte con su información completa
|
# Obtener una parte con su información completa
|
||||||
part = session.query(GPart).options(
|
part = session.query(Part).options(
|
||||||
joinedload(GPart.country),
|
joinedload(Part.country),
|
||||||
joinedload(GPart.currency),
|
joinedload(Part.currency),
|
||||||
joinedload(GPart.part_class_info).joinedload(GClass.material_type)
|
joinedload(Part.part_class_info).joinedload(Class.material_type)
|
||||||
).filter(
|
).filter(
|
||||||
GPart.client_key == 1,
|
Part.client_key == 1,
|
||||||
GPart.part_number == "PART001"
|
Part.part_number == "PART001"
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
# Acceder a los datos relacionados
|
# Acceder a los datos relacionados
|
||||||
@@ -89,7 +89,7 @@ class PartDetailResponseDTO(BaseModel):
|
|||||||
|
|
||||||
## Consideraciones Técnicas
|
## Consideraciones Técnicas
|
||||||
|
|
||||||
1. **Composite Foreign Keys**: La relación entre `GPart` y `GClass` usa claves foráneas compuestas que requieren `primaryjoin` personalizado.
|
1. **Composite Foreign Keys**: La relación entre `Part` y `Class` usa claves foráneas compuestas que requieren `primaryjoin` personalizado.
|
||||||
|
|
||||||
2. **Viewonly Relationships**: Algunas relaciones están marcadas como `viewonly=True` para evitar problemas de escritura accidental.
|
2. **Viewonly Relationships**: Algunas relaciones están marcadas como `viewonly=True` para evitar problemas de escritura accidental.
|
||||||
|
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ Se han actualizado todos los modelos en `api/v1/modules/a76/` para usar el schem
|
|||||||
| Módulo | Tabla | Schema | Estado |
|
| Módulo | Tabla | Schema | Estado |
|
||||||
|--------|-------|---------|---------|
|
|--------|-------|---------|---------|
|
||||||
| **Company** | `gcompany` | `a76` | ✅ Actualizada |
|
| **Company** | `gcompany` | `a76` | ✅ Actualizada |
|
||||||
| **Client & Provider** | `gclient_provider` | `a76` | ✅ Actualizada |
|
| **Client & Provider** | `client_provider` | `a76` | ✅ Actualizada |
|
||||||
| **Client & Provider** | `gclient_provider_address` | `a76` | ✅ Actualizada |
|
| **Client & Provider** | `gclient_provider_address` | `a76` | ✅ Actualizada |
|
||||||
| **Client & Provider** | `gclient_provider_programs` | `a76` | ✅ Actualizada |
|
| **Client & Provider** | `gclient_provider_programs` | `a76` | ✅ Actualizada |
|
||||||
| **GParts** | `gparts` | `a76` | ✅ Actualizada |
|
| **GParts** | `parts` | `a76` | ✅ Actualizada |
|
||||||
| **GClass** | `gclasses` | `a76` | ✅ Actualizada |
|
| **Class** | `classes` | `a76` | ✅ Actualizada |
|
||||||
| **Licenses** | `licenses` | `a76` | ✅ Ya estaba |
|
| **Licenses** | `licenses` | `a76` | ✅ Ya estaba |
|
||||||
| **Licenses** | `license_usage` | `a76` | ✅ Ya estaba |
|
| **Licenses** | `license_usage` | `a76` | ✅ Ya estaba |
|
||||||
| **Tenants** | `tenants` | `a76` | ✅ Ya estaba |
|
| **Tenants** | `tenants` | `a76` | ✅ Ya estaba |
|
||||||
@@ -39,10 +39,10 @@ class GCompany(Base):
|
|||||||
#### 2. Foreign Keys Actualizadas
|
#### 2. Foreign Keys Actualizadas
|
||||||
```python
|
```python
|
||||||
# ANTES
|
# ANTES
|
||||||
client_id = Column(String(8), ForeignKey('gclient_provider.client_id'), ...)
|
client_id = Column(String(8), ForeignKey('client_provider.client_id'), ...)
|
||||||
|
|
||||||
# DESPUÉS
|
# DESPUÉS
|
||||||
client_id = Column(String(8), ForeignKey('a76.gclient_provider.client_id'), ...)
|
client_id = Column(String(8), ForeignKey('a76.client_provider.client_id'), ...)
|
||||||
```
|
```
|
||||||
|
|
||||||
### 🏗️ Estructura de Schemas
|
### 🏗️ Estructura de Schemas
|
||||||
@@ -60,11 +60,11 @@ PostgreSQL Database
|
|||||||
├── licenses
|
├── licenses
|
||||||
├── license_usage
|
├── license_usage
|
||||||
├── gcompany
|
├── gcompany
|
||||||
├── gclient_provider
|
├── client_provider
|
||||||
├── gclient_provider_address
|
├── gclient_provider_address
|
||||||
├── gclient_provider_programs
|
├── gclient_provider_programs
|
||||||
├── gparts
|
├── parts
|
||||||
└── gclasses
|
└── classes
|
||||||
```
|
```
|
||||||
|
|
||||||
### 🔗 Relaciones Mantenidas
|
### 🔗 Relaciones Mantenidas
|
||||||
@@ -77,13 +77,13 @@ Las relaciones entre schemas funcionan correctamente:
|
|||||||
|
|
||||||
#### Ejemplos de Relaciones Cross-Schema:
|
#### Ejemplos de Relaciones Cross-Schema:
|
||||||
```python
|
```python
|
||||||
# GPart (a76) → Country (public)
|
# Part (a76) → Country (public)
|
||||||
country_of_origin = Column(String(3), ForeignKey('public.countries.m3_key'))
|
country_of_origin = Column(String(3), ForeignKey('public.countries.m3_key'))
|
||||||
|
|
||||||
# GPart (a76) → CurrencyType (public)
|
# Part (a76) → CurrencyType (public)
|
||||||
currency_key = Column(String(3), ForeignKey('public.currency_types.code'))
|
currency_key = Column(String(3), ForeignKey('public.currency_types.code'))
|
||||||
|
|
||||||
# GClass (a76) → MaterialType (public)
|
# Class (a76) → MaterialType (public)
|
||||||
material_key = Column(String(10), ForeignKey('public.material_types.key'))
|
material_key = Column(String(10), ForeignKey('public.material_types.key'))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user