diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/__init__.py b/backend/api/v1/modules/a24/inv/inv_aphis/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/dto.py b/backend/api/v1/modules/a24/inv/inv_aphis/dto.py new file mode 100644 index 00000000..1631ba3c --- /dev/null +++ b/backend/api/v1/modules/a24/inv/inv_aphis/dto.py @@ -0,0 +1,17 @@ +from .inv_aphis_general.dto import InvPartAphisGeneralDTO +from .inv_aphis_characteristic.dto import InvPartAphisCharacteristicDTO +from .inv_aphis_stype_pitems.dto import InvPartAphisStypePitemsDTO +from .inv_aphis_lpcos.dto import InvPartAphisLpcosDTO +from .inv_aphis_entities.dto import InvPartAphisEntitiesDTO +from .inv_aphis_containers.dto import InvPartAphisContainersDTO +from .inv_aphis_routing.dto import InvPartAphisRoutingDTO + +__all__ = [ + "InvPartAphisGeneralDTO", + "InvPartAphisCharacteristicDTO", + "InvPartAphisStypePitemsDTO", + "InvPartAphisLpcosDTO", + "InvPartAphisEntitiesDTO", + "InvPartAphisContainersDTO", + "InvPartAphisRoutingDTO" +] diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_characteristic/__init__.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_characteristic/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_characteristic/dto.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_characteristic/dto.py new file mode 100644 index 00000000..d893dddc --- /dev/null +++ b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_characteristic/dto.py @@ -0,0 +1,15 @@ +from pydantic import BaseModel +from typing import Optional, List +from datetime import date + +class InvPartAphisCharacteristicDTO(BaseModel): + id: Optional[int] = None + aphis_general_id: Optional[int] = None + item_id: Optional[str] = None + number_from: Optional[str] = None + number_to: Optional[str] = None + category_type: Optional[str] = None + commodity_qua: Optional[str] = None + commodity_char_qua: Optional[str] = None + description: Optional[str] = None + category_code: Optional[str] = None diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_characteristic/models.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_characteristic/models.py new file mode 100644 index 00000000..49eb6394 --- /dev/null +++ b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_characteristic/models.py @@ -0,0 +1,35 @@ +from typing import TYPE_CHECKING, Optional, List +from datetime import date +from sqlalchemy import ( + Integer, String, Date, Numeric, Boolean, + PrimaryKeyConstraint, ForeignKeyConstraint +) +from sqlalchemy.orm import Mapped, mapped_column, relationship +from api.v1.common.base_models import TenantScopedMixin, TimestampMixin +from core.database import Base + +if TYPE_CHECKING: + from api.v1.modules.a24.inv.inv_parts.models import InvPart + +class InvPartAphisCharacteristic(Base, TenantScopedMixin, TimestampMixin): + """Módulo: Item characteristic""" + __tablename__ = "inv_aphis_characteristic" + __table_args__ = ( + PrimaryKeyConstraint("id", name="inv_aphis_characteristic_pkey"), + ForeignKeyConstraint(["aphis_general_id"], ["a24.inv_aphis_general.id"]), + {"schema": "a24", "extend_existing": True}, + ) + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) + aphis_general_id: Mapped[int] = mapped_column(Integer, nullable=False) + + # --- CAMPOS CARACTERISTICAS --- + item_id: Mapped[Optional[str]] = mapped_column(String(50)) + number_from: Mapped[Optional[str]] = mapped_column(String(50)) + number_to: Mapped[Optional[str]] = mapped_column(String(50)) + category_type: Mapped[Optional[str]] = mapped_column(String(50)) + commodity_qua: Mapped[Optional[str]] = mapped_column(String(50)) + commodity_char_qua: Mapped[Optional[str]] = mapped_column(String(50)) + description: Mapped[Optional[str]] = mapped_column(String(200)) + category_code: Mapped[Optional[str]] = mapped_column(String(50)) + + aphis_general: Mapped["InvPartAphisGeneral"] = relationship(back_populates="characteristics") diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_containers/__init__.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_containers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_containers/dto.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_containers/dto.py new file mode 100644 index 00000000..397253e6 --- /dev/null +++ b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_containers/dto.py @@ -0,0 +1,10 @@ +from pydantic import BaseModel +from typing import Optional, List +from datetime import date + +class InvPartAphisContainersDTO(BaseModel): + id: Optional[int] = None + aphis_general_id: Optional[int] = None + container_number: Optional[str] = None + length: Optional[str] = None + type: Optional[str] = None diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_containers/models.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_containers/models.py new file mode 100644 index 00000000..aca9c3a7 --- /dev/null +++ b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_containers/models.py @@ -0,0 +1,30 @@ +from typing import TYPE_CHECKING, Optional, List +from datetime import date +from sqlalchemy import ( + Integer, String, Date, Numeric, Boolean, + PrimaryKeyConstraint, ForeignKeyConstraint +) +from sqlalchemy.orm import Mapped, mapped_column, relationship +from api.v1.common.base_models import TenantScopedMixin, TimestampMixin +from core.database import Base + +if TYPE_CHECKING: + from api.v1.modules.a24.inv.inv_parts.models import InvPart + +class InvPartAphisContainers(Base, TenantScopedMixin, TimestampMixin): + """Módulo: containers""" + __tablename__ = "inv_aphis_containers" + __table_args__ = ( + PrimaryKeyConstraint("id", name="inv_aphis_containers_pkey"), + ForeignKeyConstraint(["aphis_general_id"], ["a24.inv_aphis_general.id"]), + {"schema": "a24", "extend_existing": True}, + ) + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) + aphis_general_id: Mapped[int] = mapped_column(Integer, nullable=False) + + # --- CAMPOS CONTAINERS --- + container_number: Mapped[Optional[str]] = mapped_column(String(50)) + length: Mapped[Optional[str]] = mapped_column(String(20)) + type: Mapped[Optional[str]] = mapped_column(String(50)) + + aphis_general: Mapped["InvPartAphisGeneral"] = relationship(back_populates="containers") diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_entities/__init__.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_entities/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_entities/dto.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_entities/dto.py new file mode 100644 index 00000000..1a12dcd9 --- /dev/null +++ b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_entities/dto.py @@ -0,0 +1,11 @@ +from pydantic import BaseModel +from typing import Optional, List +from datetime import date + +class InvPartAphisEntitiesDTO(BaseModel): + id: Optional[int] = None + aphis_general_id: Optional[int] = None + consignee_key: Optional[str] = None + broker_key: Optional[str] = None + lpco_auth_party_key: Optional[str] = None + grower_key: Optional[str] = None diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_entities/models.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_entities/models.py new file mode 100644 index 00000000..d9c202f6 --- /dev/null +++ b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_entities/models.py @@ -0,0 +1,31 @@ +from typing import TYPE_CHECKING, Optional, List +from datetime import date +from sqlalchemy import ( + Integer, String, Date, Numeric, Boolean, + PrimaryKeyConstraint, ForeignKeyConstraint +) +from sqlalchemy.orm import Mapped, mapped_column, relationship +from api.v1.common.base_models import TenantScopedMixin, TimestampMixin +from core.database import Base + +if TYPE_CHECKING: + from api.v1.modules.a24.inv.inv_parts.models import InvPart + +class InvPartAphisEntities(Base, TenantScopedMixin, TimestampMixin): + """Módulo: entities""" + __tablename__ = "inv_aphis_entities" + __table_args__ = ( + PrimaryKeyConstraint("id", name="inv_aphis_entities_pkey"), + ForeignKeyConstraint(["aphis_general_id"], ["a24.inv_aphis_general.id"]), + {"schema": "a24", "extend_existing": True}, + ) + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) + aphis_general_id: Mapped[int] = mapped_column(Integer, nullable=False) + + # --- CAMPOS ENTITIES --- + consignee_key: Mapped[Optional[str]] = mapped_column(String(50)) + broker_key: Mapped[Optional[str]] = mapped_column(String(50)) + lpco_auth_party_key: Mapped[Optional[str]] = mapped_column(String(50)) + grower_key: Mapped[Optional[str]] = mapped_column(String(50)) + + aphis_general: Mapped["InvPartAphisGeneral"] = relationship(back_populates="entities") diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_general/__init__.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_general/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_general/dto.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_general/dto.py new file mode 100644 index 00000000..9577f768 --- /dev/null +++ b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_general/dto.py @@ -0,0 +1,54 @@ +from pydantic import BaseModel +from typing import Optional, List +from datetime import date + +from ..inv_aphis_characteristic.dto import InvPartAphisCharacteristicDTO +from ..inv_aphis_stype_pitems.dto import InvPartAphisStypePitemsDTO +from ..inv_aphis_lpcos.dto import InvPartAphisLpcosDTO +from ..inv_aphis_entities.dto import InvPartAphisEntitiesDTO +from ..inv_aphis_containers.dto import InvPartAphisContainersDTO +from ..inv_aphis_routing.dto import InvPartAphisRoutingDTO + +# NOTA: Importar primero los sub-dtos para resolver dependencias + +class InvPartAphisGeneralDTO(BaseModel): + id: Optional[int] = None + program_code: Optional[str] = None + processing_code: Optional[str] = None + aphis_type: Optional[str] = None + disclaimer: Optional[str] = None + electronic_image: Optional[str] = None + confidential: Optional[str] = None + global_product_id: Optional[str] = None + intended_use_code: Optional[str] = None + intended_use_description: Optional[str] = None + item_type: Optional[str] = None + product_code: Optional[str] = None + product_code_2: Optional[str] = None + product_code_3: Optional[str] = None + scientific_genus_name: Optional[str] = None + scientific_species_name: Optional[str] = None + scientific_sub_species_name: Optional[str] = None + common_name_specific: Optional[str] = None + common_name_general: Optional[str] = None + signed_doc: Optional[str] = None + signed_doc_date: Optional[date] = None + signed_doc_id: Optional[str] = None + invoice_number: Optional[str] = None + quantity_1: Optional[str] = None + quantity_2: Optional[str] = None + quantity_3: Optional[str] = None + inspection: Optional[str] = None + inspection_date: Optional[date] = None + inspection_loc_date: Optional[date] = None + inspection_location: Optional[str] = None + country_production: Optional[str] = None + country_source: Optional[str] = None + + # Relaciones + characteristics: List[InvPartAphisCharacteristicDTO] = [] + stype_pitems: List[InvPartAphisStypePitemsDTO] = [] + lpcos: List[InvPartAphisLpcosDTO] = [] + entities: List[InvPartAphisEntitiesDTO] = [] + containers: List[InvPartAphisContainersDTO] = [] + routing: List[InvPartAphisRoutingDTO] = [] diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_general/models.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_general/models.py new file mode 100644 index 00000000..d7aba57e --- /dev/null +++ b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_general/models.py @@ -0,0 +1,72 @@ +from typing import TYPE_CHECKING, Optional, List +from datetime import date +from sqlalchemy import ( + Integer, String, Date, Numeric, Boolean, + PrimaryKeyConstraint, ForeignKeyConstraint +) +from sqlalchemy.orm import Mapped, mapped_column, relationship +from api.v1.common.base_models import TenantScopedMixin, TimestampMixin +from core.database import Base + +if TYPE_CHECKING: + from api.v1.modules.a24.inv.inv_parts.models import InvPart + +class InvPartAphisGeneral(Base, TenantScopedMixin, TimestampMixin): + """ + Tabla principal de Aphis vinculada a la parte. + Pestaña: Información general de aphis + """ + __tablename__ = "inv_aphis_general" + __table_args__ = ( + PrimaryKeyConstraint("id", name="inv_aphis_general_pkey"), + ForeignKeyConstraint( + ["inv_part_id"], ["a24.inv_partes.id"], name="fk_aphis_general_inv_part" + ), + {"schema": "a24", "extend_existing": True}, + ) + + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) + inv_part_id: Mapped[int] = mapped_column(Integer, nullable=False) + + # --- 31 CAMPOS INICIALES --- + program_code: Mapped[Optional[str]] = mapped_column(String(10)) + processing_code: Mapped[Optional[str]] = mapped_column(String(10)) + aphis_type: Mapped[Optional[str]] = mapped_column(String(10)) + disclaimer: Mapped[Optional[str]] = mapped_column(String(10)) + electronic_image: Mapped[Optional[str]] = mapped_column(String(50)) + confidential: Mapped[Optional[str]] = mapped_column(String(1)) + global_product_id: Mapped[Optional[str]] = mapped_column(String(100)) + intended_use_code: Mapped[Optional[str]] = mapped_column(String(10)) + intended_use_description: Mapped[Optional[str]] = mapped_column(String(200)) + item_type: Mapped[Optional[str]] = mapped_column(String(20)) + product_code: Mapped[Optional[str]] = mapped_column(String(20)) + product_code_2: Mapped[Optional[str]] = mapped_column(String(20)) + product_code_3: Mapped[Optional[str]] = mapped_column(String(20)) + scientific_genus_name: Mapped[Optional[str]] = mapped_column(String(100)) + scientific_species_name: Mapped[Optional[str]] = mapped_column(String(100)) + scientific_sub_species_name: Mapped[Optional[str]] = mapped_column(String(100)) + common_name_specific: Mapped[Optional[str]] = mapped_column(String(200)) + common_name_general: Mapped[Optional[str]] = mapped_column(String(200)) + signed_doc: Mapped[Optional[str]] = mapped_column(String(100)) + signed_doc_date: Mapped[Optional[date]] = mapped_column(Date) + signed_doc_id: Mapped[Optional[str]] = mapped_column(String(50)) + invoice_number: Mapped[Optional[str]] = mapped_column(String(50)) + quantity_1: Mapped[Optional[str]] = mapped_column(String(50)) + quantity_2: Mapped[Optional[str]] = mapped_column(String(50)) + quantity_3: Mapped[Optional[str]] = mapped_column(String(50)) + inspection: Mapped[Optional[str]] = mapped_column(String(200)) + inspection_date: Mapped[Optional[date]] = mapped_column(Date) + inspection_loc_date: Mapped[Optional[date]] = mapped_column(Date) + inspection_location: Mapped[Optional[str]] = mapped_column(String(200)) + country_production: Mapped[Optional[str]] = mapped_column(String(3)) + country_source: Mapped[Optional[str]] = mapped_column(String(3)) + + # Relaciones + inv_part: Mapped["InvPart"] = relationship("InvPart", back_populates="aphis_records") + + characteristics: Mapped[List["InvPartAphisCharacteristic"]] = relationship(back_populates="aphis_general", cascade="all, delete-orphan") + stype_pitems: Mapped[List["InvPartAphisStypePitems"]] = relationship(back_populates="aphis_general", cascade="all, delete-orphan") + lpcos: Mapped[List["InvPartAphisLpcos"]] = relationship(back_populates="aphis_general", cascade="all, delete-orphan") + entities: Mapped[List["InvPartAphisEntities"]] = relationship(back_populates="aphis_general", cascade="all, delete-orphan") + containers: Mapped[List["InvPartAphisContainers"]] = relationship(back_populates="aphis_general", cascade="all, delete-orphan") + routing: Mapped[List["InvPartAphisRouting"]] = relationship(back_populates="aphis_general", cascade="all, delete-orphan") diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_lpcos/__init__.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_lpcos/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_lpcos/dto.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_lpcos/dto.py new file mode 100644 index 00000000..06bd466e --- /dev/null +++ b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_lpcos/dto.py @@ -0,0 +1,18 @@ +from pydantic import BaseModel +from typing import Optional, List +from datetime import date + +class InvPartAphisLpcosDTO(BaseModel): + id: Optional[int] = None + aphis_general_id: Optional[int] = None + issuer: Optional[str] = None + issuer_loc_qua: Optional[str] = None + issuer_loc: Optional[str] = None + issuer_loc_desc: Optional[str] = None + uom: Optional[str] = None + txn_type: Optional[str] = None + type: Optional[str] = None + number: Optional[str] = None + date_qual: Optional[str] = None + date: Optional[date] = None + qty: Optional[str] = None diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_lpcos/models.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_lpcos/models.py new file mode 100644 index 00000000..7dddd2b4 --- /dev/null +++ b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_lpcos/models.py @@ -0,0 +1,38 @@ +from typing import TYPE_CHECKING, Optional, List +from datetime import date +from sqlalchemy import ( + Integer, String, Date, Numeric, Boolean, + PrimaryKeyConstraint, ForeignKeyConstraint +) +from sqlalchemy.orm import Mapped, mapped_column, relationship +from api.v1.common.base_models import TenantScopedMixin, TimestampMixin +from core.database import Base + +if TYPE_CHECKING: + from api.v1.modules.a24.inv.inv_parts.models import InvPart + +class InvPartAphisLpcos(Base, TenantScopedMixin, TimestampMixin): + """Módulo: lpcos""" + __tablename__ = "inv_aphis_lpcos" + __table_args__ = ( + PrimaryKeyConstraint("id", name="inv_aphis_lpcos_pkey"), + ForeignKeyConstraint(["aphis_general_id"], ["a24.inv_aphis_general.id"]), + {"schema": "a24", "extend_existing": True}, + ) + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) + aphis_general_id: Mapped[int] = mapped_column(Integer, nullable=False) + + # --- CAMPOS LPCO --- + issuer: Mapped[Optional[str]] = mapped_column(String(100)) + issuer_loc_qua: Mapped[Optional[str]] = mapped_column(String(50)) + issuer_loc: Mapped[Optional[str]] = mapped_column(String(50)) + issuer_loc_desc: Mapped[Optional[str]] = mapped_column(String(200)) + uom: Mapped[Optional[str]] = mapped_column(String(20)) + txn_type: Mapped[Optional[str]] = mapped_column(String(50)) + type: Mapped[Optional[str]] = mapped_column(String(50)) + number: Mapped[Optional[str]] = mapped_column(String(50)) + date_qual: Mapped[Optional[str]] = mapped_column(String(50)) + date: Mapped[Optional[date]] = mapped_column(Date) + qty: Mapped[Optional[str]] = mapped_column(String(50)) + + aphis_general: Mapped["InvPartAphisGeneral"] = relationship(back_populates="lpcos") diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_routing/__init__.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_routing/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_routing/dto.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_routing/dto.py new file mode 100644 index 00000000..c145f080 --- /dev/null +++ b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_routing/dto.py @@ -0,0 +1,10 @@ +from pydantic import BaseModel +from typing import Optional, List +from datetime import date + +class InvPartAphisRoutingDTO(BaseModel): + id: Optional[int] = None + aphis_general_id: Optional[int] = None + type: Optional[str] = None + country: Optional[str] = None + name: Optional[str] = None diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_routing/models.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_routing/models.py new file mode 100644 index 00000000..83ecaeb2 --- /dev/null +++ b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_routing/models.py @@ -0,0 +1,30 @@ +from typing import TYPE_CHECKING, Optional, List +from datetime import date +from sqlalchemy import ( + Integer, String, Date, Numeric, Boolean, + PrimaryKeyConstraint, ForeignKeyConstraint +) +from sqlalchemy.orm import Mapped, mapped_column, relationship +from api.v1.common.base_models import TenantScopedMixin, TimestampMixin +from core.database import Base + +if TYPE_CHECKING: + from api.v1.modules.a24.inv.inv_parts.models import InvPart + +class InvPartAphisRouting(Base, TenantScopedMixin, TimestampMixin): + """Módulo: routing""" + __tablename__ = "inv_aphis_routing" + __table_args__ = ( + PrimaryKeyConstraint("id", name="inv_aphis_routing_pkey"), + ForeignKeyConstraint(["aphis_general_id"], ["a24.inv_aphis_general.id"]), + {"schema": "a24", "extend_existing": True}, + ) + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) + aphis_general_id: Mapped[int] = mapped_column(Integer, nullable=False) + + # --- CAMPOS ROUTING --- + type: Mapped[Optional[str]] = mapped_column(String(50)) + country: Mapped[Optional[str]] = mapped_column(String(50)) + name: Mapped[Optional[str]] = mapped_column(String(100)) + + aphis_general: Mapped["InvPartAphisGeneral"] = relationship(back_populates="routing") diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_stype_pitems/__init__.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_stype_pitems/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_stype_pitems/dto.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_stype_pitems/dto.py new file mode 100644 index 00000000..19453965 --- /dev/null +++ b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_stype_pitems/dto.py @@ -0,0 +1,14 @@ +from pydantic import BaseModel +from typing import Optional, List +from datetime import date + +class InvPartAphisStypePitemsDTO(BaseModel): + id: Optional[int] = None + aphis_general_id: Optional[int] = None + source_type_code: Optional[str] = None + country_code: Optional[str] = None + geo_location: Optional[str] = None + processing_start: Optional[date] = None + processing_end: Optional[date] = None + processing_type: Optional[str] = None + processing_desc: Optional[str] = None diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_stype_pitems/models.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_stype_pitems/models.py new file mode 100644 index 00000000..d8a0ff5b --- /dev/null +++ b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_stype_pitems/models.py @@ -0,0 +1,34 @@ +from typing import TYPE_CHECKING, Optional, List +from datetime import date +from sqlalchemy import ( + Integer, String, Date, Numeric, Boolean, + PrimaryKeyConstraint, ForeignKeyConstraint +) +from sqlalchemy.orm import Mapped, mapped_column, relationship +from api.v1.common.base_models import TenantScopedMixin, TimestampMixin +from core.database import Base + +if TYPE_CHECKING: + from api.v1.modules.a24.inv.inv_parts.models import InvPart + +class InvPartAphisStypePitems(Base, TenantScopedMixin, TimestampMixin): + """Módulo: stype_Pitems""" + __tablename__ = "inv_aphis_stype_pitems" + __table_args__ = ( + PrimaryKeyConstraint("id", name="inv_aphis_stype_pitems_pkey"), + ForeignKeyConstraint(["aphis_general_id"], ["a24.inv_aphis_general.id"]), + {"schema": "a24", "extend_existing": True}, + ) + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) + aphis_general_id: Mapped[int] = mapped_column(Integer, nullable=False) + + # --- CAMPOS STYPE PITEMS --- + source_type_code: Mapped[Optional[str]] = mapped_column(String(50)) + country_code: Mapped[Optional[str]] = mapped_column(String(3)) + geo_location: Mapped[Optional[str]] = mapped_column(String(100)) + processing_start: Mapped[Optional[date]] = mapped_column(Date) + processing_end: Mapped[Optional[date]] = mapped_column(Date) + processing_type: Mapped[Optional[str]] = mapped_column(String(50)) + processing_desc: Mapped[Optional[str]] = mapped_column(String(200)) + + aphis_general: Mapped["InvPartAphisGeneral"] = relationship(back_populates="stype_pitems") diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/models.py b/backend/api/v1/modules/a24/inv/inv_aphis/models.py new file mode 100644 index 00000000..26ca531b --- /dev/null +++ b/backend/api/v1/modules/a24/inv/inv_aphis/models.py @@ -0,0 +1,17 @@ +from .inv_aphis_general.models import InvPartAphisGeneral +from .inv_aphis_characteristic.models import InvPartAphisCharacteristic +from .inv_aphis_stype_pitems.models import InvPartAphisStypePitems +from .inv_aphis_lpcos.models import InvPartAphisLpcos +from .inv_aphis_entities.models import InvPartAphisEntities +from .inv_aphis_containers.models import InvPartAphisContainers +from .inv_aphis_routing.models import InvPartAphisRouting + +__all__ = [ + "InvPartAphisGeneral", + "InvPartAphisCharacteristic", + "InvPartAphisStypePitems", + "InvPartAphisLpcos", + "InvPartAphisEntities", + "InvPartAphisContainers", + "InvPartAphisRouting" +] diff --git a/backend/api/v1/modules/a24/inv/inv_parts/models.py b/backend/api/v1/modules/a24/inv/inv_parts/models.py index f5df0fa6..5ae08e67 100644 --- a/backend/api/v1/modules/a24/inv/inv_parts/models.py +++ b/backend/api/v1/modules/a24/inv/inv_parts/models.py @@ -2,7 +2,7 @@ Modelo ORM para datos específicos de Inventario y Manufactura (S-Partes) - Anexo 24 """ -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Optional, List from decimal import Decimal from api.v1.common.base_models import TenantScopedMixin, TimestampMixin from core.database import Base @@ -18,10 +18,10 @@ from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.orm import Mapped, mapped_column, relationship if TYPE_CHECKING: - from api.v1.modules.a76.parts.models import Part - +# Importar los modelos Aphis para asegurar su registro en SQLAlchemy +import api.v1.modules.a24.inv.inv_aphis.models as aphis_models class InvPart(Base, TenantScopedMixin, TimestampMixin): """ Tabla inv_partes: Extensión de Anexo 24 para Inventarios (SPartes). @@ -135,10 +135,23 @@ class InvPart(Base, TenantScopedMixin, TimestampMixin): origin_country: Mapped[Optional[str]] = mapped_column(String(3), default='MEX') fraction_type: Mapped[Optional[str]] = mapped_column(String(10)) + # --- NUEVOS CAMPOS EXTENSION --- + agency_code_definition: Mapped[Optional[str]] = mapped_column(String(50)) # Fila 7 + carta_porte: Mapped[Optional[str]] = mapped_column(String(100)) # Fila 10 + client_part_names: Mapped[Optional[list]] = mapped_column(JSONB, nullable=True, default=[]) # Fila 5 + part_identifiers: Mapped[Optional[list]] = mapped_column(JSONB, nullable=True, default=[]) # Fila 9 + substitute_parts: Mapped[Optional[list]] = mapped_column(JSONB, nullable=True, default=[]) # Pestaña Continuación + aphis_data: Mapped[Optional[dict]] = mapped_column(JSONB, nullable=True, default={}) # Sección Aphis en Continuación + # JSONB para almacenar clientes con restricción de no descarga non_discharge_clients: Mapped[Optional[list]] = mapped_column(JSONB, nullable=True, default=[]) # --- RELACIÓN --- + aphis_records: Mapped[List["InvPartAphisGeneral"]] = relationship( + "InvPartAphisGeneral", + back_populates="inv_part", + cascade="all, delete-orphan" + ) # Usamos string "Part" para evitar problemas de carga master_info: Mapped["Part"] = relationship("Part", back_populates="inv_data") diff --git a/backend/api/v1/modules/a76/parts/dto.py b/backend/api/v1/modules/a76/parts/dto.py index e84aa70e..486b41e9 100644 --- a/backend/api/v1/modules/a76/parts/dto.py +++ b/backend/api/v1/modules/a76/parts/dto.py @@ -2,6 +2,7 @@ from datetime import datetime from decimal import Decimal from typing import List, Optional from pydantic import BaseModel, Field, ConfigDict +from api.v1.modules.a24.inv.inv_aphis.dto import InvPartAphisGeneralDTO # --- SUB-DTO: DATOS ADUANALES (FaData) --- class FaDataDTO(BaseModel): @@ -122,6 +123,16 @@ class InvDataDTO(BaseModel): sector: Optional[str] = None origin_country: Optional[str] = None fraction_type: Optional[str] = None + + # Extensiones + agency_code_definition: Optional[str] = None + carta_porte: Optional[str] = None + client_part_names: Optional[List[dict]] = None + part_identifiers: Optional[List[dict]] = None + substitute_parts: Optional[List[dict]] = None + aphis_data: Optional[dict] = None + aphis_records: Optional[List[InvPartAphisGeneralDTO]] = None + non_discharge_clients: Optional[List[dict]] = None # Lista de materiales (BOM) diff --git a/backend/api/v1/modules/a76/parts/service.py b/backend/api/v1/modules/a76/parts/service.py index 5d94ebdd..a5ef0565 100644 --- a/backend/api/v1/modules/a76/parts/service.py +++ b/backend/api/v1/modules/a76/parts/service.py @@ -43,7 +43,8 @@ class PartService: "scrap_export_fraction", "scrap_us_fraction", "equivalent_uom_2", "conversion_factor_2", "has_auxiliary", "auxiliary_uom", "auxiliary_conversion", "auxiliary_unit_cost", "mex_packing", "sales_order", "use_rule_8", "sector", "origin_country", "fraction_type", - "non_discharge_clients" + "non_discharge_clients", "agency_code_definition", "carta_porte", + "client_part_names", "part_identifiers", "substitute_parts", "aphis_data" ] @classmethod @@ -263,8 +264,10 @@ class PartService: bom_items_data = inv_dict.pop('bom_items', None) countries_data = None + aphis_records_data = None if inv_dict: countries_data = inv_dict.pop('countries', None) + aphis_records_data = inv_dict.pop('aphis_records', None) # Actualizar campos directos for key, value in data.items(): @@ -340,6 +343,81 @@ class PartService: ) db_part.inv_countries.append(country_obj) + # Actualizar registros Aphis (Esquema relacional) + if aphis_records_data is not None: + from api.v1.modules.a24.inv.inv_aphis.models import ( + InvPartAphisGeneral, + InvPartAphisCharacteristic, + InvPartAphisStypePitems, + InvPartAphisLpcos, + InvPartAphisEntities, + InvPartAphisContainers, + InvPartAphisRouting + ) + if db_part.inv_data: + # Reemplazo total de registros Aphis para esta parte + db_part.inv_data.aphis_records = [] + for a_data in aphis_records_data: + # Limpiar IDs y sub-datos para recreación + a_data.pop('id', None) + + char_data = a_data.pop('characteristics', []) + stype_data = a_data.pop('stype_pitems', []) + lpco_data = a_data.pop('lpcos', []) + entity_data = a_data.pop('entities', []) + container_data = a_data.pop('containers', []) + routing_data = a_data.pop('routing', []) + + aphis_obj = InvPartAphisGeneral( + **a_data, + tenant_id=tenant_id, + company_id=company_id + ) + + for c_data in char_data: + c_data.pop('id', None) + c_data.pop('aphis_general_id', None) + aphis_obj.characteristics.append( + InvPartAphisCharacteristic(**c_data, tenant_id=tenant_id, company_id=company_id) + ) + + for s_data in stype_data: + s_data.pop('id', None) + s_data.pop('aphis_general_id', None) + aphis_obj.stype_pitems.append( + InvPartAphisStypePitems(**s_data, tenant_id=tenant_id, company_id=company_id) + ) + + for l_data in lpco_data: + l_data.pop('id', None) + l_data.pop('aphis_general_id', None) + aphis_obj.lpcos.append( + InvPartAphisLpcos(**l_data, tenant_id=tenant_id, company_id=company_id) + ) + + for e_data in entity_data: + e_data.pop('id', None) + e_data.pop('aphis_general_id', None) + aphis_obj.entities.append( + InvPartAphisEntities(**e_data, tenant_id=tenant_id, company_id=company_id) + ) + + for con_data in container_data: + con_data.pop('id', None) + con_data.pop('aphis_general_id', None) + aphis_obj.containers.append( + InvPartAphisContainers(**con_data, tenant_id=tenant_id, company_id=company_id) + ) + + for r_data in routing_data: + r_data.pop('id', None) + r_data.pop('aphis_general_id', None) + aphis_obj.routing.append( + InvPartAphisRouting(**r_data, tenant_id=tenant_id, company_id=company_id) + ) + + db_part.inv_data.aphis_records.append(aphis_obj) + try: db.commit() db.refresh(db_part) diff --git a/frontend/src/lib/components/dashboard/goods/parts/partForm.svelte b/frontend/src/lib/components/dashboard/goods/parts/partForm.svelte index 9c7876a2..50de13c5 100644 --- a/frontend/src/lib/components/dashboard/goods/parts/partForm.svelte +++ b/frontend/src/lib/components/dashboard/goods/parts/partForm.svelte @@ -1026,33 +1026,62 @@ {isEdit ? 'Editar' : 'Nueva'} -
- {#if formType === 'both'} - - AMBOS - Gestión Inventario + Activo Fijo - - {:else if formType === 'fa'} - - SCAF - Gestión de Activo Fijo - +
+ Configuración avanzada y extensiones de datos para esta parte. +
++ Campos especiales para: {isNew ? 'Nueva Parte' : (part?.part_number || 'Cargando...')} +
+{formData.agency_info.code}
+{formData.agency_info.required}
+{formData.agency_info.program}
+{formData.agency_info.definition}
+Habilitar sugerencia automática para esta parte según criterios aduanales.
+{formData.part_number || 'SIN DEFINIR'}
+