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 - +

+ {#if isEdit} + {#if formType === 'both'} + + AMBOS + Gestión Inventario + Activo Fijo + + {:else if formType === 'fa'} + + SCAF + Gestión de Activo Fijo + + {:else} + + SCAI + Gestión de Inventario + + {/if} {:else} - - SCAI - Gestión de Inventario - +
+ Seleccione el modo: +
+ + + +
+
{/if} -

+
@@ -2599,6 +2628,28 @@ +
+
+
+ +
+
+

Campos Especiales

+

+ Configuración avanzada y extensiones de datos para esta parte. +

+
+
+ +
+

diff --git a/frontend/src/routes/dashboard/goods/parts/extension/[[id]]/+page.svelte b/frontend/src/routes/dashboard/goods/parts/extension/[[id]]/+page.svelte new file mode 100644 index 00000000..30b57345 --- /dev/null +++ b/frontend/src/routes/dashboard/goods/parts/extension/[[id]]/+page.svelte @@ -0,0 +1,2043 @@ + + +
+
+
+ +
+

Extensión de Mercancía

+

+ Campos especiales para: {isNew ? 'Nueva Parte' : (part?.part_number || 'Cargando...')} +

+
+
+ +
+ + + + + + + + Catálogo de partes + + + + Continuación + + + +
+ + + + +
+
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + + + +
+
+ + +
+
+ + +
+
+ + +
+
+ + + + +
+
+ + +
+ +
+ + + + Número de parte + Cliente + Nombre parte cliente + Acciones + + + + {#each formData.client_part_names as row, i} + + {row.part_number} + {row.client_name} + {row.part_name} + +
+ + +
+
+
+ {/each} + {#if formData.client_part_names.length === 0} + + + No hay registros asignados. Haga clic en agregar para comenzar. + + + {/if} +
+
+
+ +
+
+ + +
+
+ +
+ + % +
+
+
+
+ + + + +
+
+ + +
+ +
+
+ + + + Información de Referencia + +
+ +
+
+ +
+ + +
+
+
+ Agency Code +

{formData.agency_info.code}

+
+
+ Status +

{formData.agency_info.required}

+
+
+ +
+
+ Program Code +

{formData.agency_info.program}

+
+
+ Definition +

{formData.agency_info.definition}

+
+
+
+
+ + + + +
+
+ + +
+ +
+
+ + +
+
+ + +
+
+
+ + + + +
+
+
+

+
+ Sugerencia de regla octava +

+

Habilitar sugerencia automática para esta parte según criterios aduanales.

+
+ +
+
+
+
+
+ + + + + + + Continuación de Datos + + + Gestión de partes sustitutas y datos adicionales. + + + + +
+
+ + +
+ +
+ + + + Número de parte sustituto + Acciones + + + + {#each formData.substitute_parts as row, i} + + {row.substitute_part_number} + +
+ + +
+
+
+ {/each} + {#if formData.substitute_parts.length === 0} + + + No hay partes sustitutas asignadas. + + + {/if} +
+
+
+
+ + + + +
+ +
+
+ +
+
+ +
+ +
+
+
+
+
+
+
+
+ + + + + + {editingClientIndex !== null ? 'Editar Registro de Cliente' : 'Agregar Registro de Cliente'} + + Ingrese los detalles de la parte para el cliente seleccionado. + + + +
+
+ + +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+
+ + + + + + {editingSubstituteIndex !== null ? 'Editar Parte Sustituta' : 'Agregar Parte Sustituta'} + + Vincule una parte existente como sustituta de la original. + + + +
+
+ Número de parte original +

{formData.part_number || 'SIN DEFINIR'}

+
+ +
+ +
+ + +
+
+
+ + + + + +
+
+ + + + + +
+
+ Gestión de Registros Aphis + + Lista maestra de especificaciones Aphis para esta parte. + +
+ +
+
+ +
+
+ + + + Program Code + Processing code + Aphis type + Disclaimer + Electronic Image + Confidential + Global product id + Acciones + + + + {#each formData.aphis_data.table as row, i} + + {row.program_code || '-'} + {row.processing_code || '-'} + {row.aphis_type || '-'} + {row.disclaimer || '-'} + {row.electronic_image || '-'} + {row.confidential || '-'} + {row.global_product_id || '-'} + +
+ + +
+
+
+ {:else} + + + No hay registros Aphis. Haga clic en agregar para comenzar. + + + {/each} +
+
+
+
+ + + + +
+
+ + + + + + {editingAphisIndex !== null ? 'Editar Detalle Aphis' : 'Nuevo Registro Aphis'} + + Complete la información técnica en cada una de las pestañas. + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+ Identificadores Base +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+
+ Uso y Tipo de Item +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+
+ Taxonomía y Nombres +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+
+ Documentación y Facturación +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+
+ Cantidades e Inspección +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+
+ Origen de la Mercancía +
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ Item Characteristic +
+ +
+
+ + + + Item ID + Number from + Number to + Category type + Commodity qua + Comm char qua + Description + Category code + Acciones + + + + {#each aphisFormData.characteristics as row, i} + + {row.item_id || '-'} + {row.number_from || '-'} + {row.number_to || '-'} + {row.category_type || '-'} + {row.commodity_qua || '-'} + {row.commodity_char_qua || '-'} + {row.description || '-'} + {row.category_code || '-'} + +
+ + +
+
+
+ {:else} + + + No hay características asignadas a este registro. + + + {/each} +
+
+
+
+ +
+
+ Stype_Pitems +
+ +
+
+ + + + Source type + Country + Geo location + Start + End + Processing type + Description + Acciones + + + + {#each aphisFormData.stype_pitems as row, i} + + {row.source_type_code || '-'} + {row.country_code || '-'} + {row.geo_location || '-'} + {row.processing_start || '-'} + {row.processing_end || '-'} + {row.processing_type || '-'} + {row.processing_desc || '-'} + +
+ + +
+
+
+ {:else} + + + No hay registros de stype asignados. + + + {/each} +
+
+
+
+ +
+
+ LPCO Details +
+ +
+
+ + + + Issuer + Loc Qua + Loc + Loc Desc + Type + Number + Txn Type + UOM + Date Qual + Date + Qty + Acciones + + + + {#each aphisFormData.lpcos as row, i} + + {row.issuer || '-'} + {row.issuer_loc_qua || '-'} + {row.issuer_loc || '-'} + {row.issuer_loc_desc || '-'} + {row.type || '-'} + {row.number || '-'} + {row.txn_type || '-'} + {row.uom || '-'} + {row.date_qual || '-'} + {row.date || '-'} + {row.qty || '-'} + +
+ + +
+
+
+ {:else} + + + No hay registros LPCO asignados. + + + {/each} +
+
+
+
+ +
+
+ Entities +
+ +
+
+ + + + Consignee Key + Broker Key + LPCO Auth Party Key + Grower Key + Acciones + + + + {#each aphisFormData.entities as row, i} + + {row.consignee_key || '-'} + {row.broker_key || '-'} + {row.lpco_auth_party_key || '-'} + {row.grower_key || '-'} + +
+ + +
+
+
+ {:else} + + + No hay entidades asignadas. + + + {/each} +
+
+
+
+ +
+
+ Containers +
+ +
+
+ + + + Container Number + Length + Type + Acciones + + + + {#each aphisFormData.containers as row, i} + + {row.container_number || '-'} + {row.length || '-'} + {row.type || '-'} + +
+ + +
+
+
+ {:else} + + + No hay contenedores asignados. + + + {/each} +
+
+
+
+ +
+
+ Routing +
+ +
+
+ + + + Type + Country + Name + Acciones + + + + {#each aphisFormData.routing as row, i} + + {row.type || '-'} + {row.country || '-'} + {row.name || '-'} + +
+ + +
+
+
+ {:else} + + + No hay destinos/rutas asignados. + + + {/each} +
+
+
+
+
+
+
+ + + + + +
+
+ + + + + + + + + {editingAphisCharacteristicIndex !== null ? 'Editar Característica' : 'Agregar Característica'} + + Ingrese los detalles de la característica para este registro Aphis. + + + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + + + + +
+
+ + + + + + {editingAphisStypeIndex !== null ? 'Editar Stype Pitems' : 'Agregar Stype Pitems'} + + Ingrese los detalles de procesamiento para este registro Aphis. + + + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + + + + +
+
+ + + + + + {editingAphisLpcoIndex !== null ? 'Editar LPCO' : 'Agregar LPCO'} + + Ingrese los detalles del documento LPCO para este registro Aphis. + + + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + + + + +
+
+ + + + + + {editingAphisEntitiesIndex !== null ? 'Editar Entidad' : 'Agregar Entidad'} + + Ingrese los detalles de las entidades relacionadas para este registro Aphis. + + + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + + + + +
+
+ + + + + + {editingAphisContainersIndex !== null ? 'Editar Contenedor' : 'Agregar Contenedor'} + + Ingrese los detalles del contenedor para este registro Aphis. + + + +
+
+ + +
+
+ + +
+
+ + +
+
+ + + + + +
+
+ + + + + + {editingAphisRoutingIndex !== null ? 'Editar Ruta' : 'Agregar Ruta'} + + Ingrese los detalles de la ruta para este registro Aphis. + + + +
+
+ + +
+
+ + +
+
+ + +
+
+ + + + + +
+