diff --git a/backend/api/v1/modules/a76/general_catalogs/company/__init__.py b/backend/api/v1/modules/a76/general_catalogs/company/__init__.py index 6f72af22..936a85fb 100644 --- a/backend/api/v1/modules/a76/general_catalogs/company/__init__.py +++ b/backend/api/v1/modules/a76/general_catalogs/company/__init__.py @@ -1,7 +1,24 @@ """ Módulo de Company """ +from .models import Company +from .submodels import ( + CompanyAddress, + CompanyCertification, + CompanyDigitalCertificate, + CompanyVU, + CompanyElectronicAgent, + CompanyPrevalidator, + CompanyCFDI, +) -from .routes import router - -__all__ = ["router"] +__all__ = [ + "Company", + "CompanyAddress", + "CompanyCertification", + "CompanyDigitalCertificate", + "CompanyVU", + "CompanyElectronicAgent", + "CompanyPrevalidator", + "CompanyCFDI", +] \ No newline at end of file diff --git a/backend/api/v1/modules/a76/general_catalogs/company/models.py b/backend/api/v1/modules/a76/general_catalogs/company/models.py index 18bdd840..0d96e933 100644 --- a/backend/api/v1/modules/a76/general_catalogs/company/models.py +++ b/backend/api/v1/modules/a76/general_catalogs/company/models.py @@ -1,53 +1,57 @@ """ -Modelos ORM para gestión de empresa +Modelo principal de Company """ - -from typing import Optional - -from api.v1.common.base_models import TimestampMixin +from typing import Optional, TYPE_CHECKING +from sqlalchemy import Integer, String, SmallInteger, ForeignKey +from sqlalchemy.orm import Mapped, mapped_column, relationship from core.database import Base -from sqlalchemy import ( - Boolean, - ForeignKey, - ForeignKeyConstraint, - Integer, - PrimaryKeyConstraint, - SmallInteger, - String, -) -from sqlalchemy.orm import Mapped, mapped_column +from api.v1.common.base_models import TimestampMixin + +if TYPE_CHECKING: + from .submodels.address import CompanyAddress + from .submodels.certification import CompanyCertification + from .submodels.digital_certificate import CompanyDigitalCertificate + from .submodels.ventanilla_unica import CompanyVU + from .submodels.electronic_agent import CompanyElectronicAgent + from .submodels.prevalidator import CompanyPrevalidator + from .submodels.cfdi import CompanyCFDI class Company(Base, TimestampMixin): - """ - Modelo para la tabla Company - Información de la empresa - """ - - __tablename__ = "company" #GEmpresa - __table_args__ = ( - PrimaryKeyConstraint("id", name="company_pkey"), - {"schema": "a76"}, - ) - + """Información principal de la empresa""" + + __tablename__ = "company" + __table_args__ = {"schema": "a76"} + # Primary key id: Mapped[int] = mapped_column(Integer, primary_key=True) - tenant_id: Mapped[int] = mapped_column(Integer, ForeignKey("core.tenants.id"), nullable=False, index=True) - - # Información básica de la empresa - name: Mapped[Optional[str]] = mapped_column(String(255)) + tenant_id: Mapped[int] = mapped_column( + ForeignKey("core.tenants.id"), + nullable=False, + index=True + ) + + # Información básica + name: Mapped[Optional[str]] = mapped_column(String(256)) rfc: Mapped[Optional[str]] = mapped_column(String(30)) - main_activity: Mapped[Optional[str]] = mapped_column(String(255)) - - # Información del programa - program: Mapped[Optional[str]] = mapped_column(String(10)) + curp: Mapped[Optional[str]] = mapped_column(String(19)) + main_activity: Mapped[Optional[str]] = mapped_column(String(80)) + + # Programa + program: Mapped[Optional[str]] = mapped_column(String(7)) program_number: Mapped[Optional[str]] = mapped_column(String(40)) prosec: Mapped[Optional[int]] = mapped_column(SmallInteger) prosec_authorization: Mapped[Optional[str]] = mapped_column(String(20)) - + + # Sectores + sector1: Mapped[Optional[str]] = mapped_column(String(150)) + sector2: Mapped[Optional[str]] = mapped_column(String(150)) + sector3: Mapped[Optional[str]] = mapped_column(String(5)) + # Identificadores manufacturer_id: Mapped[Optional[str]] = mapped_column(String(25)) - broker_company: Mapped[Optional[str]] = mapped_column(String(10)) - + broker_company: Mapped[Optional[str]] = mapped_column(String(6)) + # Responsable responsible: Mapped[Optional[str]] = mapped_column(String(80)) responsible_name: Mapped[Optional[str]] = mapped_column(String(20)) @@ -55,24 +59,88 @@ class Company(Base, TimestampMixin): responsible_mother_last_name: Mapped[Optional[str]] = mapped_column(String(20)) responsible_rfc: Mapped[Optional[str]] = mapped_column(String(30)) position: Mapped[Optional[str]] = mapped_column(String(30)) - - # Configuración - logo: Mapped[Optional[str]] = mapped_column(String(500)) - has_express_line: Mapped[Optional[bool]] = mapped_column(Boolean) + + # Configuración básica + logo: Mapped[Optional[str]] = mapped_column(String(255)) + has_express_line: Mapped[Optional[str]] = mapped_column(String(2)) order_format_type: Mapped[Optional[str]] = mapped_column(String(19)) - previous_code: Mapped[Optional[int]] = mapped_column(SmallInteger) - is_service_company: Mapped[Optional[bool]] = mapped_column(Boolean) - - # Cliente y submaquila + is_service_company: Mapped[Optional[str]] = mapped_column(String(2)) client_name: Mapped[Optional[str]] = mapped_column(String(300)) subassembly_mode: Mapped[Optional[str]] = mapped_column(String(7)) - - # Información adicional - curp: Mapped[Optional[str]] = mapped_column(String(19)) + + # Configuraciones técnicas (flags) + previous_code: Mapped[Optional[int]] = mapped_column(SmallInteger) + active_labels: Mapped[Optional[int]] = mapped_column(SmallInteger) + active_fractions: Mapped[Optional[int]] = mapped_column(SmallInteger) + activate_caat: Mapped[Optional[int]] = mapped_column(SmallInteger) + trans_interface: Mapped[Optional[int]] = mapped_column(SmallInteger) + american_costs: Mapped[Optional[int]] = mapped_column(SmallInteger) + scaf_readonly: Mapped[Optional[int]] = mapped_column(SmallInteger) + parts_replacement: Mapped[Optional[int]] = mapped_column(SmallInteger) + activate_facmexame: Mapped[Optional[int]] = mapped_column(SmallInteger) + part_reference: Mapped[Optional[int]] = mapped_column(SmallInteger) + international_firm: Mapped[Optional[int]] = mapped_column(SmallInteger) + + # Configuraciones simples + ftp_key: Mapped[Optional[str]] = mapped_column(String(10)) + sifra_path: Mapped[Optional[str]] = mapped_column(String(255)) + version_type: Mapped[Optional[str]] = mapped_column(String(20)) + sql_language: Mapped[Optional[str]] = mapped_column(String(19)) + balance_operation_mode: Mapped[Optional[str]] = mapped_column(String(50)) inter_db_name: Mapped[Optional[str]] = mapped_column(String(100)) - ctpat_svi: Mapped[Optional[str]] = mapped_column(String(100)) - trusted_exporter_number: Mapped[Optional[str]] = mapped_column(String(50)) - prevalidator_key: Mapped[Optional[str]] = mapped_column(String(20)) - seventh_amendment: Mapped[Optional[bool]] = mapped_column( - Boolean - ) # FINALCONTADORAELECTRONICO renombrado + + # ==================== RELACIONES CON SUBTABLAS ==================== + + addresses: Mapped[list["CompanyAddress"]] = relationship( + "CompanyAddress", + back_populates="company", + cascade="all, delete-orphan", + lazy="selectin" + ) + + certification: Mapped[Optional["CompanyCertification"]] = relationship( + "CompanyCertification", + back_populates="company", + uselist=False, + cascade="all, delete-orphan", + lazy="selectin" + ) + + digital_certificates: Mapped[list["CompanyDigitalCertificate"]] = relationship( + "CompanyDigitalCertificate", + back_populates="company", + cascade="all, delete-orphan", + lazy="selectin" + ) + + ventanilla_unica: Mapped[Optional["CompanyVU"]] = relationship( + "CompanyVU", + back_populates="company", + uselist=False, + cascade="all, delete-orphan", + lazy="selectin" + ) + + electronic_agent: Mapped[Optional["CompanyElectronicAgent"]] = relationship( + "CompanyElectronicAgent", + back_populates="company", + uselist=False, + cascade="all, delete-orphan", + lazy="selectin" + ) + + prevalidator: Mapped[Optional["CompanyPrevalidator"]] = relationship( + "CompanyPrevalidator", + back_populates="company", + uselist=False, + cascade="all, delete-orphan", + lazy="selectin" + ) + + cfdi: Mapped[Optional["CompanyCFDI"]] = relationship( + "CompanyCFDI", + back_populates="company", + uselist=False, + cascade="all, delete-orphan", + lazy="selectin" + ) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/general_catalogs/company/submodels/__init__.py b/backend/api/v1/modules/a76/general_catalogs/company/submodels/__init__.py new file mode 100644 index 00000000..579ab456 --- /dev/null +++ b/backend/api/v1/modules/a76/general_catalogs/company/submodels/__init__.py @@ -0,0 +1,20 @@ +""" +Submodelos de Company +""" +from .address import CompanyAddress +from .certification import CompanyCertification +from .digital_certificate import CompanyDigitalCertificate +from .companyVU import CompanyVU +from .electronic_agent import CompanyElectronicAgent +from .prevalidator import CompanyPrevalidator +from .cfdi import CompanyCFDI + +__all__ = [ + "CompanyAddress", + "CompanyCertification", + "CompanyDigitalCertificate", + "CompanyVU", + "CompanyElectronicAgent", + "CompanyPrevalidator", + "CompanyCFDI", +] \ No newline at end of file diff --git a/backend/api/v1/modules/a76/general_catalogs/company/submodels/address.py b/backend/api/v1/modules/a76/general_catalogs/company/submodels/address.py new file mode 100644 index 00000000..3ecfe266 --- /dev/null +++ b/backend/api/v1/modules/a76/general_catalogs/company/submodels/address.py @@ -0,0 +1,46 @@ +""" +Modelo de direcciones de empresa +""" +from typing import Optional, TYPE_CHECKING +from sqlalchemy import Integer, String, ForeignKey +from sqlalchemy.orm import Mapped, mapped_column, relationship +from core.database import Base +from api.v1.common.base_models import TimestampMixin + +if TYPE_CHECKING: + from ..models import Company + + +class CompanyAddress(Base, TimestampMixin): + """Direcciones de la empresa (principal, industrial 1 y 2)""" + + __tablename__ = "company_address" + __table_args__ = {"schema": "a76"} + + id: Mapped[int] = mapped_column(Integer, primary_key=True) + company_id: Mapped[int] = mapped_column( + ForeignKey("a76.company.id", ondelete="CASCADE"), + nullable=False, + index=True + ) + address_type: Mapped[str] = mapped_column(String(20)) # 'main', 'industrial', 'industrial2' + + # Campos de dirección + street: Mapped[Optional[str]] = mapped_column(String(100)) + exterior_number: Mapped[Optional[str]] = mapped_column(String(20)) + interior_number: Mapped[Optional[str]] = mapped_column(String(20)) + postal_code: Mapped[Optional[str]] = mapped_column(String(15)) + neighborhood: Mapped[Optional[str]] = mapped_column(String(40)) + city: Mapped[Optional[str]] = mapped_column(String(40)) + municipality: Mapped[Optional[str]] = mapped_column(String(50)) + state: Mapped[Optional[str]] = mapped_column(String(30)) + country: Mapped[Optional[str]] = mapped_column(String(4)) + phone: Mapped[Optional[str]] = mapped_column(String(30)) + fax: Mapped[Optional[str]] = mapped_column(String(30)) + email: Mapped[Optional[str]] = mapped_column(String(100)) + + # Relación inversa + company: Mapped["Company"] = relationship( + "Company", + back_populates="addresses" + ) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/general_catalogs/company/submodels/certification.py b/backend/api/v1/modules/a76/general_catalogs/company/submodels/certification.py new file mode 100644 index 00000000..5624704b --- /dev/null +++ b/backend/api/v1/modules/a76/general_catalogs/company/submodels/certification.py @@ -0,0 +1,52 @@ +""" +Modelo de certificaciones de empresa +""" +from typing import Optional, TYPE_CHECKING +from sqlalchemy import Integer, String, SmallInteger, ForeignKey +from sqlalchemy.orm import Mapped, mapped_column, relationship +from core.database import Base +from api.v1.common.base_models import TimestampMixin + +if TYPE_CHECKING: + from ..models import Company + + +class CompanyCertification(Base, TimestampMixin): + """Certificaciones de la empresa""" + + __tablename__ = "company_certification" + __table_args__ = {"schema": "a76"} + + id: Mapped[int] = mapped_column(Integer, primary_key=True) + company_id: Mapped[int] = mapped_column( + ForeignKey("a76.company.id", ondelete="CASCADE"), + nullable=False, + unique=True, + index=True + ) + + # Certificación general + is_certified_company: Mapped[Optional[str]] = mapped_column(String(1)) + certified_company_registration: Mapped[Optional[str]] = mapped_column(String(40)) + certified_company_start_date: Mapped[Optional[int]] = mapped_column(Integer) + certified_company_end_date: Mapped[Optional[int]] = mapped_column(Integer) + + # Anexo 31 + annex31_certification_date: Mapped[Optional[int]] = mapped_column(Integer) + annex31_certification_number: Mapped[Optional[str]] = mapped_column(String(50)) + annex31_modality: Mapped[Optional[str]] = mapped_column(String(50)) + annex31_company_type: Mapped[Optional[str]] = mapped_column(String(50)) + annex31_renewal_date: Mapped[Optional[int]] = mapped_column(Integer) + annex31_final_certification_date: Mapped[Optional[int]] = mapped_column(Integer) + + # Otras certificaciones + is_oea_company: Mapped[Optional[int]] = mapped_column(SmallInteger) + ctpat_svi: Mapped[Optional[str]] = mapped_column(String(100)) + trusted_exporter_number: Mapped[Optional[str]] = mapped_column(String(50)) + neec_company: Mapped[Optional[int]] = mapped_column(Integer) + + # Relación inversa + company: Mapped["Company"] = relationship( + "Company", + back_populates="certification" + ) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/general_catalogs/company/submodels/cfdi.py b/backend/api/v1/modules/a76/general_catalogs/company/submodels/cfdi.py new file mode 100644 index 00000000..f5e9d48f --- /dev/null +++ b/backend/api/v1/modules/a76/general_catalogs/company/submodels/cfdi.py @@ -0,0 +1,37 @@ +""" +Modelo de configuración de CFDI (Facturación Electrónica) +""" +from typing import Optional, TYPE_CHECKING +from sqlalchemy import Integer, String, ForeignKey +from sqlalchemy.orm import Mapped, mapped_column, relationship +from core.database import Base +from api.v1.common.base_models import TimestampMixin + +if TYPE_CHECKING: + from ..models import Company + + +class CompanyCFDI(Base, TimestampMixin): + """Configuración de Facturación Electrónica (CFDI)""" + + __tablename__ = "company_cfdi" + __table_args__ = {"schema": "a76"} + + id: Mapped[int] = mapped_column(Integer, primary_key=True) + company_id: Mapped[int] = mapped_column( + ForeignKey("a76.company.id", ondelete="CASCADE"), + nullable=False, + unique=True, + index=True + ) + + # Rutas de aplicaciones y archivos + xml_save_path: Mapped[Optional[str]] = mapped_column(String(5000)) + cfdi_app_path: Mapped[Optional[str]] = mapped_column(String(5000)) + pac_app_path: Mapped[Optional[str]] = mapped_column(String(5000)) + + # Relación inversa + company: Mapped["Company"] = relationship( + "Company", + back_populates="cfdi" + ) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/general_catalogs/company/submodels/digital_certificate.py b/backend/api/v1/modules/a76/general_catalogs/company/submodels/digital_certificate.py new file mode 100644 index 00000000..6de413b9 --- /dev/null +++ b/backend/api/v1/modules/a76/general_catalogs/company/submodels/digital_certificate.py @@ -0,0 +1,44 @@ +""" +Modelo de certificados digitales (FIEL, CFDI, Cancelación) +""" +from typing import Optional, TYPE_CHECKING +from sqlalchemy import Integer, String, ForeignKey +from sqlalchemy.orm import Mapped, mapped_column, relationship +from core.database import Base +from api.v1.common.base_models import TimestampMixin + +if TYPE_CHECKING: + from ..models import Company + + +class CompanyDigitalCertificate(Base, TimestampMixin): + """Certificados digitales (FIEL, CFDI)""" + + __tablename__ = "company_digital_certificate" + __table_args__ = {"schema": "a76"} + + id: Mapped[int] = mapped_column(Integer, primary_key=True) + company_id: Mapped[int] = mapped_column( + ForeignKey("a76.company.id", ondelete="CASCADE"), + nullable=False, + index=True + ) + certificate_type: Mapped[str] = mapped_column(String(20)) # 'fiel', 'cfdi', 'cancellation' + + # Rutas de archivos + cer_file_path: Mapped[Optional[str]] = mapped_column(String(5000)) + key_file_path: Mapped[Optional[str]] = mapped_column(String(5000)) + + # Contraseñas y claves + password: Mapped[Optional[str]] = mapped_column(String(200)) + access_key: Mapped[Optional[str]] = mapped_column(String(50)) + + # Fechas de expiración + cer_expiration_date: Mapped[Optional[int]] = mapped_column(Integer) + key_expiration_date: Mapped[Optional[int]] = mapped_column(Integer) + + # Relación inversa + company: Mapped["Company"] = relationship( + "Company", + back_populates="digital_certificates" + ) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/general_catalogs/company/submodels/electronic_agent.py b/backend/api/v1/modules/a76/general_catalogs/company/submodels/electronic_agent.py new file mode 100644 index 00000000..d57c9c0c --- /dev/null +++ b/backend/api/v1/modules/a76/general_catalogs/company/submodels/electronic_agent.py @@ -0,0 +1,46 @@ +""" +Modelo de configuración del Agente Aduanal Electrónico +""" +from typing import Optional, TYPE_CHECKING +from sqlalchemy import Integer, String, ForeignKey +from sqlalchemy.orm import Mapped, mapped_column, relationship +from core.database import Base +from api.v1.common.base_models import TimestampMixin + +if TYPE_CHECKING: + from ..models import Company + + +class CompanyElectronicAgent(Base, TimestampMixin): + """Configuración de Agente Aduanal Electrónico""" + + __tablename__ = "company_electronic_agent" + __table_args__ = {"schema": "a76"} + + id: Mapped[int] = mapped_column(Integer, primary_key=True) + company_id: Mapped[int] = mapped_column( + ForeignKey("a76.company.id", ondelete="CASCADE"), + nullable=False, + unique=True, + index=True + ) + + # Carpetas + input_folder: Mapped[Optional[str]] = mapped_column(String(1000)) + output_folder: Mapped[Optional[str]] = mapped_column(String(1000)) + + # Máscaras y extensiones + send_mask: Mapped[Optional[str]] = mapped_column(String(20)) + response_mask: Mapped[Optional[str]] = mapped_column(String(20)) + response_extension: Mapped[Optional[str]] = mapped_column(String(20)) + + # Contadores + counter_start: Mapped[Optional[int]] = mapped_column(Integer) + counter_end: Mapped[Optional[int]] = mapped_column(Integer) + counter_next: Mapped[Optional[int]] = mapped_column(Integer) + + # Relación inversa + company: Mapped["Company"] = relationship( + "Company", + back_populates="electronic_agent" + ) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/general_catalogs/company/submodels/prevalidator.py b/backend/api/v1/modules/a76/general_catalogs/company/submodels/prevalidator.py new file mode 100644 index 00000000..05da674b --- /dev/null +++ b/backend/api/v1/modules/a76/general_catalogs/company/submodels/prevalidator.py @@ -0,0 +1,38 @@ +""" +Modelo de configuración del Prevalidador Aduanal +""" +from typing import Optional, TYPE_CHECKING +from sqlalchemy import Integer, String, ForeignKey +from sqlalchemy.orm import Mapped, mapped_column, relationship +from core.database import Base +from api.v1.common.base_models import TimestampMixin + +if TYPE_CHECKING: + from ..models import Company + + +class CompanyPrevalidator(Base, TimestampMixin): + """Configuración de Prevalidador Aduanal""" + + __tablename__ = "company_prevalidator" + __table_args__ = {"schema": "a76"} + + id: Mapped[int] = mapped_column(Integer, primary_key=True) + company_id: Mapped[int] = mapped_column( + ForeignKey("a76.company.id", ondelete="CASCADE"), + nullable=False, + unique=True, + index=True + ) + + # Información del prevalidador + customs: Mapped[Optional[str]] = mapped_column(String(20)) + key: Mapped[Optional[str]] = mapped_column(String(20)) + patent: Mapped[Optional[str]] = mapped_column(String(4)) + description: Mapped[Optional[str]] = mapped_column(String(100)) + + # Relación inversa + company: Mapped["Company"] = relationship( + "Company", + back_populates="prevalidator" + ) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/general_catalogs/company/submodels/vu.py b/backend/api/v1/modules/a76/general_catalogs/company/submodels/vu.py new file mode 100644 index 00000000..d094e598 --- /dev/null +++ b/backend/api/v1/modules/a76/general_catalogs/company/submodels/vu.py @@ -0,0 +1,50 @@ +""" +Modelo de configuración de Ventanilla Única +""" +from typing import Optional, TYPE_CHECKING +from sqlalchemy import Integer, String, ForeignKey +from sqlalchemy.orm import Mapped, mapped_column, relationship +from core.database import Base +from api.v1.common.base_models import TimestampMixin + +if TYPE_CHECKING: + from ..models import Company + + +class CompanyVU(Base, TimestampMixin): + """Configuración de Ventanilla Única""" + + __tablename__ = "CompanyVU" + __table_args__ = {"schema": "a76"} + + id: Mapped[int] = mapped_column(Integer, primary_key=True) + company_id: Mapped[int] = mapped_column( + ForeignKey("a76.company.id", ondelete="CASCADE"), + nullable=False, + unique=True, + index=True + ) + + # Credenciales + webservice_user: Mapped[Optional[str]] = mapped_column(String(100)) + webservice_password: Mapped[Optional[str]] = mapped_column(String(100)) + email: Mapped[Optional[str]] = mapped_column(String(800)) + + # Configuración + figure_type: Mapped[Optional[str]] = mapped_column(String(29)) + central_path: Mapped[Optional[str]] = mapped_column(String(1499)) + xml_files_path: Mapped[Optional[str]] = mapped_column(String(1499)) + + # RFC y validación + query_rfc: Mapped[Optional[str]] = mapped_column(String(30)) + validation_rfc: Mapped[Optional[str]] = mapped_column(String(30)) + + # Otras configuraciones + configuration_source: Mapped[Optional[str]] = mapped_column(String(30)) + measurement_units: Mapped[Optional[str]] = mapped_column(String(3)) + + # Relación inversa + company: Mapped["Company"] = relationship( + "Company", + back_populates="ventanilla_unica" + ) \ No newline at end of file