From 18b0a493226546d66544fcbd1f6f6552bad5a113 Mon Sep 17 00:00:00 2001 From: Kevin_Ramirez Date: Tue, 23 Dec 2025 17:55:12 -0600 Subject: [PATCH] Desarrollo del sistema de crud, ademas de homogenizar el estilo de las tablas --- .../modules/a76/general_catalogs/doda/dto.py | 4 +- .../electronic_notices/dto.py | 2 +- .../a76/general_catalogs/equivalencies/dto.py | 13 +- .../general_catalogs/equivalencies/models.py | 2 + .../general_catalogs/equivalencies/service.py | 194 +++++++--- .../general_catalogs/identifiers/service.py | 2 +- .../a76/general_catalogs/inpc/service.py | 2 +- .../general_catalogs/locations/__init__.py | 0 .../a76/general_catalogs/prevalidators/dto.py | 29 -- .../general_catalogs/prevalidators/service.py | 2 +- .../a76/general_catalogs/signatures/dto.py | 2 +- .../general_catalogs/signatures/service.py | 18 +- .../unit_conversions/service.py | 34 +- backend/check_db.py | 30 ++ check_db.py | 30 ++ check_uom.py | 23 ++ docker-compose.yml | 2 +- .../dashboard/a76/general_catalogs/doda.ts | 51 ++- .../general_catalogs/electronic-notices.ts | 34 +- .../a76/general_catalogs/equivalencies.ts | 85 ++-- .../a76/general_catalogs/error-catalogs.ts | 53 ++- .../dashboard/a76/general_catalogs/inpc.ts | 30 +- .../a76/general_catalogs/locations.ts | 109 +++--- .../a76/general_catalogs/prevalidators.ts | 35 +- .../a76/general_catalogs/signatures.ts | 6 +- .../a76/general_catalogs/unit-conversions.ts | 6 +- .../general_catalogs/doda/columns.ts | 43 +++ .../doda/create-edit-dialog.svelte | 362 +++++++++++------- .../doda/data-table-actions.svelte | 85 ++++ .../general_catalogs/doda/data-table.svelte | 102 +++++ .../electronic-notices/columns.ts | 43 +++ .../create-edit-dialog.svelte | 205 +++++----- .../data-table-actions.svelte | 85 ++++ .../electronic-notices/data-table.svelte | 104 +++++ .../general_catalogs/equivalencies/columns.ts | 33 ++ .../equivalencies/create-edit-dialog.svelte | 127 ++++++ .../equivalencies/data-table-actions.svelte | 90 +++++ .../equivalencies/data-table.svelte | 104 +++++ .../equivalency/create-edit-dialog.svelte | 137 +++++++ .../error_catalogs/columns.ts | 31 ++ .../error_catalogs/create-edit-dialog.svelte | 178 +++++++++ .../create-edite-dialoge.svelte | 165 -------- .../error_catalogs/data-table-actions.svelte | 97 +++++ .../error_catalogs/data-table.svelte | 106 +++++ .../general_catalogs/identifiers/columns.ts | 38 ++ .../identifiers/create-edit-dialog.svelte | 130 +++++++ .../identifiers/data-table-actions.svelte | 85 ++++ .../identifiers/data-table.svelte | 104 +++++ .../general_catalogs/inpc/columns.ts | 33 ++ .../inpc/create-edit-dialog.svelte | 128 +++++++ .../inpc/data-table-actions.svelte | 85 ++++ .../general_catalogs/inpc/data-table.svelte | 104 +++++ .../locations/create-edit-dialog.svelte | 126 ++++++ .../locations/create-edite-dialog.svelte | 166 -------- .../general_catalogs/prevalidators/columns.ts | 38 ++ .../prevalidators/create-edit-dialog.svelte | 153 ++------ .../prevalidators/data-table-actions.svelte | 85 ++++ .../prevalidators/data-table.svelte | 102 +++++ .../general_catalogs/signatures/columns.ts | 32 ++ .../signatures/data-table-actions.svelte | 93 +++++ .../signatures/data-table.svelte | 106 +++++ .../unit_conversions/columns.ts | 15 +- .../components/dashboard/locations/columns.ts | 20 +- .../locations/data-table-actions.svelte | 97 +++++ .../general_catalogs/doda/+page.server.ts | 19 +- .../general_catalogs/doda/+page.svelte | 45 +-- .../electronic_notices/+page.server.ts | 29 +- .../electronic_notices/+page.svelte | 43 +-- .../equivalencies/+page.server.ts | 33 +- .../equivalencies/+page.svelte | 59 +-- .../error_catalogs/+page.server.ts | 22 +- .../error_catalogs/+page.svelte | 33 +- .../exchange-rate/+page.svelte | 132 ++++--- .../identifiers/+page.server.ts | 77 ++-- .../general_catalogs/identifiers/+page.svelte | 127 +++--- .../general_catalogs/inpc/+page.server.ts | 33 +- .../general_catalogs/inpc/+page.svelte | 45 +-- .../general_catalogs/legends/+page.svelte | 14 +- .../locations/+page.server.ts | 92 +++-- .../general_catalogs/locations/+page.svelte | 91 +++-- .../prevalidators/+page.server.ts | 13 +- .../prevalidators/+page.svelte | 101 +++-- .../signatures/+page.server.ts | 14 +- .../general_catalogs/signatures/+page.svelte | 92 +++-- .../unit_conversions/+page.server.ts | 5 +- .../units_of_measure/ace/+page.svelte | 127 +++--- .../units_of_measure/american/+page.svelte | 127 +++--- .../units_of_measure/customs/+page.svelte | 89 +++-- .../units_of_measure/general/+page.svelte | 127 +++--- .../units_of_measure/oma/+page.svelte | 127 +++--- .../reference_data/incoterms/+page.svelte | 237 ++++-------- 91 files changed, 4745 insertions(+), 1843 deletions(-) create mode 100644 backend/api/v1/modules/a76/general_catalogs/locations/__init__.py create mode 100644 backend/check_db.py create mode 100644 check_db.py create mode 100644 check_uom.py create mode 100644 frontend/src/lib/components/dashboard/general_catalogs/doda/columns.ts create mode 100644 frontend/src/lib/components/dashboard/general_catalogs/doda/data-table-actions.svelte create mode 100644 frontend/src/lib/components/dashboard/general_catalogs/doda/data-table.svelte create mode 100644 frontend/src/lib/components/dashboard/general_catalogs/electronic-notices/columns.ts create mode 100644 frontend/src/lib/components/dashboard/general_catalogs/electronic-notices/data-table-actions.svelte create mode 100644 frontend/src/lib/components/dashboard/general_catalogs/electronic-notices/data-table.svelte create mode 100644 frontend/src/lib/components/dashboard/general_catalogs/equivalencies/columns.ts create mode 100644 frontend/src/lib/components/dashboard/general_catalogs/equivalencies/create-edit-dialog.svelte create mode 100644 frontend/src/lib/components/dashboard/general_catalogs/equivalencies/data-table-actions.svelte create mode 100644 frontend/src/lib/components/dashboard/general_catalogs/equivalencies/data-table.svelte create mode 100644 frontend/src/lib/components/dashboard/general_catalogs/equivalency/create-edit-dialog.svelte create mode 100644 frontend/src/lib/components/dashboard/general_catalogs/error_catalogs/columns.ts create mode 100644 frontend/src/lib/components/dashboard/general_catalogs/error_catalogs/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/error_catalogs/create-edite-dialoge.svelte create mode 100644 frontend/src/lib/components/dashboard/general_catalogs/error_catalogs/data-table-actions.svelte create mode 100644 frontend/src/lib/components/dashboard/general_catalogs/error_catalogs/data-table.svelte create mode 100644 frontend/src/lib/components/dashboard/general_catalogs/identifiers/columns.ts create mode 100644 frontend/src/lib/components/dashboard/general_catalogs/identifiers/create-edit-dialog.svelte create mode 100644 frontend/src/lib/components/dashboard/general_catalogs/identifiers/data-table-actions.svelte create mode 100644 frontend/src/lib/components/dashboard/general_catalogs/identifiers/data-table.svelte create mode 100644 frontend/src/lib/components/dashboard/general_catalogs/inpc/columns.ts create mode 100644 frontend/src/lib/components/dashboard/general_catalogs/inpc/create-edit-dialog.svelte create mode 100644 frontend/src/lib/components/dashboard/general_catalogs/inpc/data-table-actions.svelte create mode 100644 frontend/src/lib/components/dashboard/general_catalogs/inpc/data-table.svelte create mode 100644 frontend/src/lib/components/dashboard/general_catalogs/locations/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/locations/create-edite-dialog.svelte create mode 100644 frontend/src/lib/components/dashboard/general_catalogs/prevalidators/columns.ts create mode 100644 frontend/src/lib/components/dashboard/general_catalogs/prevalidators/data-table-actions.svelte create mode 100644 frontend/src/lib/components/dashboard/general_catalogs/prevalidators/data-table.svelte create mode 100644 frontend/src/lib/components/dashboard/general_catalogs/signatures/columns.ts create mode 100644 frontend/src/lib/components/dashboard/general_catalogs/signatures/data-table-actions.svelte create mode 100644 frontend/src/lib/components/dashboard/general_catalogs/signatures/data-table.svelte create mode 100644 frontend/src/lib/components/dashboard/locations/data-table-actions.svelte diff --git a/backend/api/v1/modules/a76/general_catalogs/doda/dto.py b/backend/api/v1/modules/a76/general_catalogs/doda/dto.py index ad1b3e07..0d188199 100644 --- a/backend/api/v1/modules/a76/general_catalogs/doda/dto.py +++ b/backend/api/v1/modules/a76/general_catalogs/doda/dto.py @@ -340,7 +340,7 @@ class DodaUpdateDTO(BaseModel): class DodaResponseDTO(BaseModel): """DTO para responder con datos de un DODA""" - sys_id: int + id: int integration_number: Optional[str] = None doda_date: Optional[int] = None doda_time: Optional[int] = None @@ -383,7 +383,7 @@ class DodaResponseDTO(BaseModel): class DodaDetailResponseDTO(BaseModel): """DTO detallado para responder con todos los datos de un DODA""" - sys_id: int + id: int integration_number: Optional[str] = None doda_date: Optional[int] = None doda_time: Optional[int] = None diff --git a/backend/api/v1/modules/a76/general_catalogs/electronic_notices/dto.py b/backend/api/v1/modules/a76/general_catalogs/electronic_notices/dto.py index 523fad87..97edc4a5 100644 --- a/backend/api/v1/modules/a76/general_catalogs/electronic_notices/dto.py +++ b/backend/api/v1/modules/a76/general_catalogs/electronic_notices/dto.py @@ -68,7 +68,7 @@ class ElectronicNoticeUpdateDTO(BaseModel): class ElectronicNoticeResponseDTO(BaseModel): """DTO para responder con datos de un aviso electrónico""" - sys_id: int + id: int notice_number: Optional[str] = None year: Optional[str] = None patent: Optional[str] = None diff --git a/backend/api/v1/modules/a76/general_catalogs/equivalencies/dto.py b/backend/api/v1/modules/a76/general_catalogs/equivalencies/dto.py index 31fe5f57..f2017259 100644 --- a/backend/api/v1/modules/a76/general_catalogs/equivalencies/dto.py +++ b/backend/api/v1/modules/a76/general_catalogs/equivalencies/dto.py @@ -30,22 +30,27 @@ class EquivalencyItemResponse(EquivalencyItemBase): class EquivalencyBase(BaseModel): - identifier: str = Field(..., max_length=10, description="Identifier") + fraccion_mex: str = Field(..., max_length=10, description="Fraccion MX (Identifier)") + fraccion_us: str = Field(..., max_length=100, description="Fraccion US (External Field)") description: Optional[str] = Field( None, max_length=200, description="Description") class EquivalencyCreate(EquivalencyBase): - items: Optional[List[EquivalencyItemCreate]] = [] + pass class EquivalencyUpdate(BaseModel): - identifier: Optional[str] = Field(None, max_length=10) + fraccion_mex: Optional[str] = Field(None, max_length=10) + fraccion_us: Optional[str] = Field(None, max_length=100) description: Optional[str] = Field(None, max_length=200) class EquivalencyResponse(EquivalencyBase): id: int - items: List[EquivalencyItemResponse] = [] + tenant_id: int + company_id: int + + model_config = ConfigDict(from_attributes=True) model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a76/general_catalogs/equivalencies/models.py b/backend/api/v1/modules/a76/general_catalogs/equivalencies/models.py index 1d4b3d7c..5211bc10 100644 --- a/backend/api/v1/modules/a76/general_catalogs/equivalencies/models.py +++ b/backend/api/v1/modules/a76/general_catalogs/equivalencies/models.py @@ -16,7 +16,9 @@ class Equivalency(Base, TenantScopedMixin, TimestampMixin): id: Mapped[int] = mapped_column( Integer, primary_key=True, autoincrement=True) + identifier: Mapped[str] = mapped_column(String(10), nullable=False) + description: Mapped[Optional[str]] = mapped_column( String(200), nullable=True) diff --git a/backend/api/v1/modules/a76/general_catalogs/equivalencies/service.py b/backend/api/v1/modules/a76/general_catalogs/equivalencies/service.py index 16a626f9..3580ef22 100644 --- a/backend/api/v1/modules/a76/general_catalogs/equivalencies/service.py +++ b/backend/api/v1/modules/a76/general_catalogs/equivalencies/service.py @@ -1,6 +1,7 @@ from typing import List, Optional, Tuple, Dict, Any -from sqlalchemy.orm import Session +from sqlalchemy.orm import Session, joinedload from sqlalchemy import select +from sqlalchemy.exc import IntegrityError from fastapi import HTTPException from .models import Equivalency, EquivalencyItem @@ -20,14 +21,26 @@ class EquivalencyService: query = db.query(Equivalency).filter( Equivalency.tenant_id == tenant_id, Equivalency.company_id == company_id - ) + ).options(joinedload(Equivalency.items)) if filters: - # Add filters here if needed - pass + if 'fraccion_mex' in filters: + query = query.filter(Equivalency.identifier.ilike(f"%{filters['fraccion_mex']}%")) + if 'description' in filters: + query = query.filter(Equivalency.description.ilike(f"%{filters['description']}%")) total = query.count() items = query.offset(skip).limit(limit).all() + + # Map internal fields to DTO fields + for item in items: + item.fraccion_mex = item.identifier + # Try to find the first item to get fraccion_us + if item.items: + item.fraccion_us = item.items[0].external_field + else: + item.fraccion_us = "" + return items, total @staticmethod @@ -37,11 +50,20 @@ class EquivalencyService: tenant_id: int, company_id: int ) -> Optional[Equivalency]: - return db.query(Equivalency).filter( + item = db.query(Equivalency).filter( Equivalency.id == id, Equivalency.tenant_id == tenant_id, Equivalency.company_id == company_id - ).first() + ).options(joinedload(Equivalency.items)).first() + + if item: + item.fraccion_mex = item.identifier + if item.items: + item.fraccion_us = item.items[0].external_field + else: + item.fraccion_us = "" + + return item @staticmethod def create( @@ -50,50 +72,122 @@ class EquivalencyService: tenant_id: int, company_id: int ) -> Equivalency: - db_obj = Equivalency( - identifier=data.identifier, - description=data.description, - tenant_id=tenant_id, - company_id=company_id - ) - db.add(db_obj) - db.commit() - db.refresh(db_obj) + try: + # Create Parent + db_obj = Equivalency( + identifier=data.fraccion_mex, + description=data.description, + tenant_id=tenant_id, + company_id=company_id + ) + db.add(db_obj) + db.flush() # Flush to get ID - if data.items: - for item_data in data.items: - item = EquivalencyItem( - **item_data.model_dump(), - equivalency_id=db_obj.id, - tenant_id=tenant_id, - company_id=company_id - ) - db.add(item) + # Create Child Item (mapping fraccion_mex -> original_field, fraccion_us -> external_field) + item = EquivalencyItem( + equivalency_id=db_obj.id, + original_field=data.fraccion_mex, # Must exist in units_of_measure + external_field=data.fraccion_us, + tenant_id=tenant_id, + company_id=company_id + ) + db.add(item) + db.commit() db.refresh(db_obj) - - return db_obj + + # Map for response + db_obj.fraccion_mex = db_obj.identifier + db_obj.fraccion_us = item.external_field + + return db_obj + except IntegrityError as e: + db.rollback() + error_msg = str(e.orig) if hasattr(e, 'orig') else str(e) + print(f"IntegrityError in create: {error_msg}") + + if "units_of_measure" in error_msg: + raise HTTPException( + status_code=400, + detail=f"La Fracción MX '{data.fraccion_mex}' no es válida. Debe existir en el catálogo de Unidades de Medida." + ) + if "uq_equivalency_identifier" in error_msg: + raise HTTPException( + status_code=400, + detail=f"Ya existe una equivalencia para la Fracción MX '{data.fraccion_mex}'." + ) + raise HTTPException(status_code=400, detail=f"Error al guardar: {error_msg}") + except Exception as e: + db.rollback() + print(f"Error in create: {str(e)}") + raise e @staticmethod def update( db: Session, id: int, - data: EquivalencyUpdate, tenant_id: int, + data: EquivalencyUpdate, company_id: int ) -> Optional[Equivalency]: db_obj = EquivalencyService.get_by_id(db, id, tenant_id, company_id) if not db_obj: return None - update_dict = data.model_dump(exclude_unset=True) - for key, value in update_dict.items(): - if key != 'items': # Handle items separately if needed, or ignore for now as per original code - setattr(db_obj, key, value) + try: + if data.fraccion_mex: + db_obj.identifier = data.fraccion_mex + if data.description: + db_obj.description = data.description - db.commit() - db.refresh(db_obj) - return db_obj + # Update Item + item = None + if db_obj.items: + item = db_obj.items[0] + + if item: + if data.fraccion_us: + item.external_field = data.fraccion_us + if data.fraccion_mex: + item.original_field = data.fraccion_mex + else: + # Create if missing + if data.fraccion_us or data.fraccion_mex: + item = EquivalencyItem( + equivalency_id=db_obj.id, + original_field=data.fraccion_mex or db_obj.identifier, + external_field=data.fraccion_us or "", + tenant_id=tenant_id, + company_id=company_id + ) + db.add(item) + + db.commit() + db.refresh(db_obj) + + # Map for response + db_obj.fraccion_mex = db_obj.identifier + if db_obj.items: + db_obj.fraccion_us = db_obj.items[0].external_field + else: + db_obj.fraccion_us = "" + + return db_obj + except IntegrityError as e: + db.rollback() + error_msg = str(e.orig) if hasattr(e, 'orig') else str(e) + print(f"IntegrityError in update: {error_msg}") + + if "units_of_measure" in error_msg: + raise HTTPException( + status_code=400, + detail=f"La Fracción MX '{data.fraccion_mex or db_obj.identifier}' no es válida. Debe existir en el catálogo de Unidades de Medida." + ) + raise HTTPException(status_code=400, detail=f"Error al actualizar: {error_msg}") + except Exception as e: + db.rollback() + print(f"Error in update: {str(e)}") + raise e @staticmethod def delete( @@ -110,7 +204,6 @@ class EquivalencyService: db.commit() return True - class EquivalencyItemService: @staticmethod def get_all( @@ -125,11 +218,6 @@ class EquivalencyItemService: EquivalencyItem.tenant_id == tenant_id, EquivalencyItem.company_id == company_id ) - - if filters and "equivalency_id" in filters: - query = query.filter( - EquivalencyItem.equivalency_id == filters["equivalency_id"]) - total = query.count() items = query.offset(skip).limit(limit).all() return items, total @@ -154,13 +242,6 @@ class EquivalencyItemService: tenant_id: int, company_id: int ) -> EquivalencyItem: - # Note: equivalency_id should be in data or handled by the caller if it's a nested route - # But TenantCRUDRoutes for child resources might pass it in filters or we need to handle it. - # For now, assuming it's in data or we don't use child resource feature yet. - - # If using child resource, the parent_id is usually passed in the path. - # But TenantCRUDRoutes passes the body. - db_obj = EquivalencyItem( **data.model_dump(), tenant_id=tenant_id, @@ -180,8 +261,9 @@ class EquivalencyItemService: company_id: int ) -> EquivalencyItem: db_obj = EquivalencyItem( - **data.model_dump(), equivalency_id=equivalency_id, + original_field=data.original_field, + external_field=data.external_field, tenant_id=tenant_id, company_id=company_id ) @@ -194,19 +276,17 @@ class EquivalencyItemService: def update( db: Session, id: int, - data: EquivalencyItemUpdate, tenant_id: int, + data: EquivalencyItemUpdate, company_id: int ) -> Optional[EquivalencyItem]: - db_obj = EquivalencyItemService.get_by_id( - db, id, tenant_id, company_id) + db_obj = EquivalencyItemService.get_by_id(db, id, tenant_id, company_id) if not db_obj: return None - - update_dict = data.model_dump(exclude_unset=True) - for key, value in update_dict.items(): + + for key, value in data.model_dump(exclude_unset=True).items(): setattr(db_obj, key, value) - + db.commit() db.refresh(db_obj) return db_obj @@ -218,11 +298,9 @@ class EquivalencyItemService: tenant_id: int, company_id: int ) -> bool: - db_obj = EquivalencyItemService.get_by_id( - db, id, tenant_id, company_id) + db_obj = EquivalencyItemService.get_by_id(db, id, tenant_id, company_id) if not db_obj: return False - db.delete(db_obj) db.commit() return True diff --git a/backend/api/v1/modules/a76/general_catalogs/identifiers/service.py b/backend/api/v1/modules/a76/general_catalogs/identifiers/service.py index 7aecde17..c4ff7578 100644 --- a/backend/api/v1/modules/a76/general_catalogs/identifiers/service.py +++ b/backend/api/v1/modules/a76/general_catalogs/identifiers/service.py @@ -68,8 +68,8 @@ class IdentifierService: def update( db: Session, id: int, - data: IdentifierUpdate, tenant_id: int, + data: IdentifierUpdate, company_id: int ) -> Optional[Identifier]: db_obj = IdentifierService.get_by_id(db, id, tenant_id, company_id) diff --git a/backend/api/v1/modules/a76/general_catalogs/inpc/service.py b/backend/api/v1/modules/a76/general_catalogs/inpc/service.py index 6336ea35..af4e6d20 100644 --- a/backend/api/v1/modules/a76/general_catalogs/inpc/service.py +++ b/backend/api/v1/modules/a76/general_catalogs/inpc/service.py @@ -63,8 +63,8 @@ class INPCService: def update( db: Session, id: int, - data: INPCUpdate, tenant_id: int, + data: INPCUpdate, company_id: int ) -> Optional[INPC]: db_obj = INPCService.get_by_id(db, id, tenant_id, company_id) diff --git a/backend/api/v1/modules/a76/general_catalogs/locations/__init__.py b/backend/api/v1/modules/a76/general_catalogs/locations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/api/v1/modules/a76/general_catalogs/prevalidators/dto.py b/backend/api/v1/modules/a76/general_catalogs/prevalidators/dto.py index 88f1d25a..1f974685 100644 --- a/backend/api/v1/modules/a76/general_catalogs/prevalidators/dto.py +++ b/backend/api/v1/modules/a76/general_catalogs/prevalidators/dto.py @@ -56,32 +56,3 @@ class PrevalidatorResponseDTO(BaseModel): class Config: from_attributes = True - - -class PrevalidatorUpdateDTO(BaseModel): - """DTO para actualizar un prevalidador""" - - customs_prevalidator: Optional[str] = Field( - None, max_length=20, description="Customs prevalidator" - ) - patent_prevalidator: Optional[str] = Field( - None, max_length=20, description="Patent prevalidator" - ) - description: Optional[str] = Field( - None, max_length=50, description="Description" - ) - - class Config: - from_attributes = True - - -class PrevalidatorResponseDTO(BaseModel): - """DTO para responder con datos de un prevalidador""" - - code: str - customs_prevalidator: Optional[str] = None - patent_prevalidator: Optional[str] = None - description: Optional[str] = None - - class Config: - from_attributes = True diff --git a/backend/api/v1/modules/a76/general_catalogs/prevalidators/service.py b/backend/api/v1/modules/a76/general_catalogs/prevalidators/service.py index a6bffe39..4f51d8f0 100644 --- a/backend/api/v1/modules/a76/general_catalogs/prevalidators/service.py +++ b/backend/api/v1/modules/a76/general_catalogs/prevalidators/service.py @@ -120,8 +120,8 @@ class PrevalidatorService: def update( db: Session, prevalidator_id: int, - prevalidator_data: PrevalidatorUpdateDTO, tenant_id: int, + prevalidator_data: PrevalidatorUpdateDTO, company_id: int ) -> Optional[Prevalidator]: """Update a prevalidator""" diff --git a/backend/api/v1/modules/a76/general_catalogs/signatures/dto.py b/backend/api/v1/modules/a76/general_catalogs/signatures/dto.py index aeee8ca3..fc317714 100644 --- a/backend/api/v1/modules/a76/general_catalogs/signatures/dto.py +++ b/backend/api/v1/modules/a76/general_catalogs/signatures/dto.py @@ -40,7 +40,7 @@ class SignatureResponseDTO(BaseModel): code: str signature: Optional[str] = None photo_path: Optional[str] = None - intenant_id: int + tenant_id: int company_id: int class Config: diff --git a/backend/api/v1/modules/a76/general_catalogs/signatures/service.py b/backend/api/v1/modules/a76/general_catalogs/signatures/service.py index 6b01bcf5..1a2a5e26 100644 --- a/backend/api/v1/modules/a76/general_catalogs/signatures/service.py +++ b/backend/api/v1/modules/a76/general_catalogs/signatures/service.py @@ -5,6 +5,8 @@ Capa de servicio para lógica de negocio de firmas import logging from typing import Any, Dict, List, Optional, Tuple +from fastapi import HTTPException, status +from sqlalchemy.exc import IntegrityError from sqlalchemy.orm import Session from . import dto, models @@ -81,10 +83,18 @@ class SignatureService: new_signature = models.Signature( **signature_data.model_dump(), tenant_id=tenant_id, company_id=company_id ) - db.add(new_signature) - db.commit() - db.refresh(new_signature) - return new_signature + try: + db.add(new_signature) + db.commit() + db.refresh(new_signature) + return new_signature + except IntegrityError as exc: + db.rollback() + # Constraint names: signatures_code_unique (code, tenant_id, company_id) + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Ya existe una firma con ese código para esta compañía", + ) from exc @staticmethod def update( diff --git a/backend/api/v1/modules/a76/general_catalogs/unit_conversions/service.py b/backend/api/v1/modules/a76/general_catalogs/unit_conversions/service.py index 11c447be..146b9729 100644 --- a/backend/api/v1/modules/a76/general_catalogs/unit_conversions/service.py +++ b/backend/api/v1/modules/a76/general_catalogs/unit_conversions/service.py @@ -1,6 +1,9 @@ from typing import List, Optional, Tuple, Dict, Any -from sqlalchemy.orm import Session + +from fastapi import HTTPException, status from sqlalchemy import select +from sqlalchemy.exc import IntegrityError +from sqlalchemy.orm import Session from .models import UnitConversion from .dto import UnitConversionCreate, UnitConversionUpdate @@ -51,10 +54,18 @@ class UnitConversionService: tenant_id=tenant_id, company_id=company_id ) - db.add(db_obj) - db.commit() - db.refresh(db_obj) - return db_obj + try: + db.add(db_obj) + db.commit() + db.refresh(db_obj) + return db_obj + except IntegrityError as exc: + db.rollback() + # Puede ser FK de unidades o duplicado de (from_unit_code, to_unit_code, tenant, company) + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Verifica que los códigos de unidad existan y que la conversión no esté duplicada", + ) from exc @staticmethod def update( @@ -72,9 +83,16 @@ class UnitConversionService: for key, value in update_dict.items(): setattr(db_obj, key, value) - db.commit() - db.refresh(db_obj) - return db_obj + try: + db.commit() + db.refresh(db_obj) + return db_obj + except IntegrityError as exc: + db.rollback() + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Verifica que los códigos de unidad existan y que la conversión no esté duplicada", + ) from exc @staticmethod def delete( diff --git a/backend/check_db.py b/backend/check_db.py new file mode 100644 index 00000000..74209b31 --- /dev/null +++ b/backend/check_db.py @@ -0,0 +1,30 @@ +import sys +import os +from sqlalchemy import create_engine, text, inspect +from sqlalchemy.orm import sessionmaker + +# Add the backend directory to the python path +sys.path.append(os.path.join(os.getcwd(), 'backend')) + +from core.config import settings + +def check_equivalencies_columns(): + engine = create_engine(str(settings.core_database_url)) + inspector = inspect(engine) + + try: + print("Checking equivalencies table columns...") + columns = inspector.get_columns('equivalencies', schema='a76') + for column in columns: + print(f"Column: {column['name']} - Type: {column['type']}") + + print("\nChecking equivalency_items table columns...") + columns = inspector.get_columns('equivalency_items', schema='a76') + for column in columns: + print(f"Column: {column['name']} - Type: {column['type']}") + + except Exception as e: + print(f"Error: {e}") + +if __name__ == "__main__": + check_equivalencies_columns() diff --git a/check_db.py b/check_db.py new file mode 100644 index 00000000..74209b31 --- /dev/null +++ b/check_db.py @@ -0,0 +1,30 @@ +import sys +import os +from sqlalchemy import create_engine, text, inspect +from sqlalchemy.orm import sessionmaker + +# Add the backend directory to the python path +sys.path.append(os.path.join(os.getcwd(), 'backend')) + +from core.config import settings + +def check_equivalencies_columns(): + engine = create_engine(str(settings.core_database_url)) + inspector = inspect(engine) + + try: + print("Checking equivalencies table columns...") + columns = inspector.get_columns('equivalencies', schema='a76') + for column in columns: + print(f"Column: {column['name']} - Type: {column['type']}") + + print("\nChecking equivalency_items table columns...") + columns = inspector.get_columns('equivalency_items', schema='a76') + for column in columns: + print(f"Column: {column['name']} - Type: {column['type']}") + + except Exception as e: + print(f"Error: {e}") + +if __name__ == "__main__": + check_equivalencies_columns() diff --git a/check_uom.py b/check_uom.py new file mode 100644 index 00000000..43776e89 --- /dev/null +++ b/check_uom.py @@ -0,0 +1,23 @@ +import sys +import os + +# Add backend to path +sys.path.append(os.path.join(os.getcwd(), 'backend')) + +from core.database import CoreSessionLocal +from api.v1.modules.a76.general_catalogs.units_of_measure.models import UnitOfMeasure + +def check_units(): + db = CoreSessionLocal() + try: + units = db.query(UnitOfMeasure).limit(10).all() + print(f"Found {len(units)} units:") + for u in units: + print(f"Code: {u.code}, Description: {u.description}") + except Exception as e: + print(f"Error: {e}") + finally: + db.close() + +if __name__ == "__main__": + check_units() diff --git a/docker-compose.yml b/docker-compose.yml index 6d4d1ffb..8ddf9f66 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -164,7 +164,7 @@ services: - KEYCLOAK_CLIENT_SECRET=${KEYCLOAK_CLIENT_SECRET:-dev-secret} - CORS_ORIGINS=${CORS_ORIGINS:-http://localhost:5173,http://localhost:3000} ports: - - "8000:8000" + - "8001:8000" depends_on: postgres-a76: condition: service_healthy diff --git a/frontend/src/lib/api/dashboard/a76/general_catalogs/doda.ts b/frontend/src/lib/api/dashboard/a76/general_catalogs/doda.ts index a6bf6a9c..45d57304 100644 --- a/frontend/src/lib/api/dashboard/a76/general_catalogs/doda.ts +++ b/frontend/src/lib/api/dashboard/a76/general_catalogs/doda.ts @@ -72,8 +72,7 @@ export interface DodaPedimentoCreate { export interface Doda { - - sys_id: number; + id: number; integration_number?: string; doda_date?: number; @@ -107,9 +106,31 @@ export interface DodaCreate { dispatch_customs?: string; customs_sections?: string; patent?: string; + pedimentos?: string; caat?: string; transport_identification?: string; fast_id?: string; + operation_type?: string; + selected?: boolean; + user_selected?: string; + last_user?: string; + responsible?: string; + carrier?: string; + shipments?: string; + pedimento_type?: string; + original_chain?: string; + serial_number?: string; + electronic_signature?: string; + transaction_number?: string; + status?: string; + linq_sat_qr?: string; + sat_certificate?: string; + sat_digital_seal?: string; + xml_doda_sent_path?: string; + xml_doda_response_path?: string; + sat_original_chain?: string; + customs_clearance?: number; + unique_badge_number?: string; } export interface DodaUpdate extends Partial {} @@ -126,32 +147,40 @@ export interface DodaListResponse { export async function getDodas( page: number = 1, pageSize: number = 50, - filters: Record = {} + filters: Record = {}, + companyId?: number ): Promise> { const params = new URLSearchParams({ page: page.toString(), page_size: pageSize.toString(), ...filters }); + if (companyId) { + params.append('company_id', companyId.toString()); + } const response = await api.get(`/v1/a76/doda?${params.toString()}`); return response.data; } -export async function getDoda(id: number): Promise { - const response = await api.get(`/v1/a76/doda/${id}`); +export async function getDoda(id: number, companyId?: number): Promise { + const params = new URLSearchParams(); + if (companyId) { + params.append('company_id', companyId.toString()); + } + const response = await api.get(`/v1/a76/doda/${id}?${params.toString()}`); return response.data; } -export async function createDoda(data: DodaCreate): Promise { - const response = await api.post('/v1/a76/doda', data); +export async function createDoda(data: DodaCreate, companyId: number): Promise { + const response = await api.post(`/v1/a76/doda?company_id=${companyId}`, data); return response.data; } -export async function updateDoda(id: number, data: DodaUpdate): Promise { - const response = await api.patch(`/v1/a76/doda/${id}`, data); +export async function updateDoda(id: number, data: DodaUpdate, companyId: number): Promise { + const response = await api.patch(`/v1/a76/doda/${id}?company_id=${companyId}`, data); return response.data; } -export async function deleteDoda(id: number): Promise { - await api.delete(`/v1/a76/doda/${id}`); +export async function deleteDoda(id: number, companyId: number): Promise { + await api.delete(`/v1/a76/doda/${id}?company_id=${companyId}`); } \ No newline at end of file diff --git a/frontend/src/lib/api/dashboard/a76/general_catalogs/electronic-notices.ts b/frontend/src/lib/api/dashboard/a76/general_catalogs/electronic-notices.ts index 921f1955..591ab614 100644 --- a/frontend/src/lib/api/dashboard/a76/general_catalogs/electronic-notices.ts +++ b/frontend/src/lib/api/dashboard/a76/general_catalogs/electronic-notices.ts @@ -17,8 +17,9 @@ export interface ElectronicNotice { certificate_number?: string; // Mixins - tenant_id?: string; - created_at?: string; + tenant_id: number; + company_id: number; + created_at: string; updated_at?: string; } @@ -49,34 +50,41 @@ export interface ElectronicNoticeListResponse { export async function getElectronicNotices( page: number = 1, pageSize: number = 50, - filters: Record = {} + filters: Record = {}, + companyId?: number ): Promise> { const params = new URLSearchParams({ page: page.toString(), page_size: pageSize.toString(), ...filters }); + if (companyId) { + params.append('company_id', companyId.toString()); + } - // Agregamos '/' al final - const response = await api.get(`/a76/electronic_notices/?${params.toString()}`); + const response = await api.get(`/v1/a76/electronic-notices/?${params.toString()}`); return response.data; } -export async function getElectronicNotice(id: number): Promise { - const response = await api.get(`/a76/electronic_notices/${id}`); +export async function getElectronicNotice(id: number, companyId?: number): Promise { + const params = new URLSearchParams(); + if (companyId) { + params.append('company_id', companyId.toString()); + } + const response = await api.get(`/v1/a76/electronic-notices/${id}?${params.toString()}`); return response.data; } -export async function createElectronicNotice(data: ElectronicNoticeCreate): Promise { - const response = await api.post('/a76/electronic_notices', data); +export async function createElectronicNotice(data: ElectronicNoticeCreate, companyId: number): Promise { + const response = await api.post(`/v1/a76/electronic-notices/?company_id=${companyId}`, data); return response.data; } -export async function updateElectronicNotice(id: number, data: ElectronicNoticeUpdate): Promise { - const response = await api.patch(`/a76/electronic_notices/${id}`, data); +export async function updateElectronicNotice(id: number, data: ElectronicNoticeUpdate, companyId: number): Promise { + const response = await api.put(`/v1/a76/electronic-notices/${id}?company_id=${companyId}`, data); return response.data; } -export async function deleteElectronicNotice(id: number): Promise { - await api.delete(`/a76/electronic_notices/${id}`); +export async function deleteElectronicNotice(id: number, companyId: number): Promise { + await api.delete(`/v1/a76/electronic-notices/${id}?company_id=${companyId}`); } \ No newline at end of file diff --git a/frontend/src/lib/api/dashboard/a76/general_catalogs/equivalencies.ts b/frontend/src/lib/api/dashboard/a76/general_catalogs/equivalencies.ts index afed400c..0b6c70c3 100644 --- a/frontend/src/lib/api/dashboard/a76/general_catalogs/equivalencies.ts +++ b/frontend/src/lib/api/dashboard/a76/general_catalogs/equivalencies.ts @@ -2,62 +2,71 @@ import { api } from '$lib/api'; import type { ApiResponse } from '$lib/api'; export interface Equivalency { - id: number; - fraccion_mex: string; - fraccion_us: string; - description?: string; - tenant_id: string; - company_id?: string; - created_at: string; - updated_at?: string; + id: number; + fraccion_mex: string; + fraccion_us: string; + description?: string; + tenant_id: number; + company_id: number; + created_at: string; + updated_at?: string; } export interface EquivalencyCreate { - fraccion_mex: string; - fraccion_us: string; - description?: string; + fraccion_mex: string; + fraccion_us: string; + description?: string; } -export interface EquivalencyUpdate extends Partial {} +export interface EquivalencyUpdate { + fraccion_mex?: string; + fraccion_us?: string; + description?: string; +} export interface EquivalencyListResponse { - items: Equivalency[]; - total: number; - page: number; - page_size: number; - pages: number; + items: Equivalency[]; + total: number; + page: number; + page_size: number; + pages: number; } export async function getEquivalencies( - page: number = 1, - pageSize: number = 50, - filters: Record = {} + page: number = 1, + pageSize: number = 50, + companyId: number, + filters: Record = {} ): Promise> { - const params = new URLSearchParams({ - page: page.toString(), - page_size: pageSize.toString(), - ...filters - }); + const params = new URLSearchParams({ + page: page.toString(), + page_size: pageSize.toString(), + company_id: companyId.toString(), + ...filters + }); - const response = await api.get(`/a76/equivalencies?${params.toString()}`); - return response.data; + return await api.get(`/v1/a76/equivalencies/?${params.toString()}`); } -export async function getEquivalency(id: number): Promise { - const response = await api.get(`/a76/equivalencies/${id}`); - return response.data; +export async function getEquivalency(id: number, companyId: number): Promise> { + return await api.get(`/v1/a76/equivalencies/${id}/?company_id=${companyId}`); } -export async function createEquivalency(data: EquivalencyCreate): Promise { - const response = await api.post('/a76/equivalencies', data); - return response.data; +export async function createEquivalency( + data: EquivalencyCreate, + companyId: number +): Promise> { + return await api.post(`/v1/a76/equivalencies/?company_id=${companyId}`, data); } -export async function updateEquivalency(id: number, data: EquivalencyUpdate): Promise { - const response = await api.patch(`/a76/equivalencies/${id}`, data); - return response.data; +export async function updateEquivalency( + id: number, + data: EquivalencyUpdate, + companyId: number +): Promise> { + return await api.put(`/v1/a76/equivalencies/${id}/?company_id=${companyId}`, data); } -export async function deleteEquivalency(id: number): Promise { - await api.delete(`/a76/equivalencies/${id}`); +export async function deleteEquivalency(id: number, companyId: number): Promise> { + return await api.delete(`/v1/a76/equivalencies/${id}/?company_id=${companyId}`); } diff --git a/frontend/src/lib/api/dashboard/a76/general_catalogs/error-catalogs.ts b/frontend/src/lib/api/dashboard/a76/general_catalogs/error-catalogs.ts index b5dc471d..f48c9139 100644 --- a/frontend/src/lib/api/dashboard/a76/general_catalogs/error-catalogs.ts +++ b/frontend/src/lib/api/dashboard/a76/general_catalogs/error-catalogs.ts @@ -1,5 +1,4 @@ import { api } from '$lib/api'; -import type { ApiResponse } from '$lib/api'; // ========================================== // ERROR CLASSIFICATION @@ -64,71 +63,83 @@ export interface ErrorCatalogListResponse { } export async function getErrorClassifications( + companyId: number, page: number = 1, pageSize: number = 50, filters: Record = {} -): Promise> { +): Promise { const params = new URLSearchParams({ page: page.toString(), page_size: pageSize.toString(), + company_id: companyId.toString(), ...filters }); - const response = await api.get(`/a76/error_classifications?${params.toString()}`); + const response = await api.get(`/v1/a76/error-catalogs/classifications/?${params.toString()}`); return response.data; } -export async function getErrorClassification(id: number): Promise { - const response = await api.get(`/a76/error_classifications/${id}`); +export async function getErrorClassification(id: number, companyId: number): Promise { + const params = new URLSearchParams({ company_id: companyId.toString() }); + const response = await api.get(`/v1/a76/error-catalogs/classifications/${id}?${params.toString()}`); return response.data; } -export async function createErrorClassification(data: ErrorClassificationCreate): Promise { - const response = await api.post('/a76/error_classifications', data); +export async function createErrorClassification(data: ErrorClassificationCreate, companyId: number): Promise { + const params = new URLSearchParams({ company_id: companyId.toString() }); + const response = await api.post(`/v1/a76/error-catalogs/classifications/?${params.toString()}`, data); return response.data; } -export async function updateErrorClassification(id: number, data: ErrorClassificationUpdate): Promise { - const response = await api.patch(`/a76/error_classifications/${id}`, data); +export async function updateErrorClassification(id: number, data: ErrorClassificationUpdate, companyId: number): Promise { + const params = new URLSearchParams({ company_id: companyId.toString() }); + const response = await api.put(`/v1/a76/error-catalogs/classifications/${id}?${params.toString()}`, data); return response.data; } -export async function deleteErrorClassification(id: number): Promise { - await api.delete(`/a76/error_classifications/${id}`); +export async function deleteErrorClassification(id: number, companyId: number): Promise { + const params = new URLSearchParams({ company_id: companyId.toString() }); + await api.delete(`/v1/a76/error-catalogs/classifications/${id}?${params.toString()}`); } // --- Catalogs --- export async function getErrorCatalogs( + companyId: number, page: number = 1, pageSize: number = 50, filters: Record = {} -): Promise> { +): Promise { const params = new URLSearchParams({ page: page.toString(), page_size: pageSize.toString(), + company_id: companyId.toString(), ...filters }); - const response = await api.get(`/a76/error_catalogs?${params.toString()}`); + const response = await api.get(`/v1/a76/error-catalogs/?${params.toString()}`); return response.data; } -export async function getErrorCatalog(id: number): Promise { - const response = await api.get(`/a76/error_catalogs/${id}`); +export async function getErrorCatalog(id: number, companyId: number): Promise { + const params = new URLSearchParams({ company_id: companyId.toString() }); + const response = await api.get(`/v1/a76/error-catalogs/${id}?${params.toString()}`); return response.data; } -export async function createErrorCatalog(data: ErrorCatalogCreate): Promise { - const response = await api.post('/a76/error_catalogs', data); +export async function createErrorCatalog(data: ErrorCatalogCreate, companyId: number): Promise { + const params = new URLSearchParams({ company_id: companyId.toString() }); + const response = await api.post(`/v1/a76/error-catalogs/?${params.toString()}`, data); return response.data; } -export async function updateErrorCatalog(id: number, data: ErrorCatalogUpdate): Promise { - const response = await api.patch(`/a76/error_catalogs/${id}`, data); +export async function updateErrorCatalog(id: number, data: ErrorCatalogUpdate, companyId: number): Promise { + const params = new URLSearchParams({ company_id: companyId.toString() }); + const response = await api.put(`/v1/a76/error-catalogs/${id}?${params.toString()}`, data); return response.data; } -export async function deleteErrorCatalog(id: number): Promise { - await api.delete(`/a76/error_catalogs/${id}`); +export async function deleteErrorCatalog(id: number, companyId: number): Promise { + const params = new URLSearchParams({ company_id: companyId.toString() }); + await api.delete(`/v1/a76/error-catalogs/${id}?${params.toString()}`); } \ No newline at end of file diff --git a/frontend/src/lib/api/dashboard/a76/general_catalogs/inpc.ts b/frontend/src/lib/api/dashboard/a76/general_catalogs/inpc.ts index 1abb0359..4720519b 100644 --- a/frontend/src/lib/api/dashboard/a76/general_catalogs/inpc.ts +++ b/frontend/src/lib/api/dashboard/a76/general_catalogs/inpc.ts @@ -16,7 +16,6 @@ export interface INPCCreate { year: string; month: string; value?: number; - } export interface INPCUpdate extends Partial {} @@ -29,7 +28,6 @@ export interface INPCListResponse { pages: number; } - export async function getINPCs( page: number = 1, pageSize: number = 50, @@ -43,36 +41,28 @@ export async function getINPCs( ...filters }); - const response = await api.get(`/v1/a76/inpc/?${params.toString()}`); - return response.data; + return await api.get(`/v1/a76/inpc/?${params.toString()}`); } - -export async function getINPC(id: number, companyId: number): Promise { - const response = await api.get(`/v1/a76/inpc/${id}/?company_id=${companyId}`); - return response.data; +export async function getINPC(id: number, companyId: number): Promise> { + return await api.get(`/v1/a76/inpc/${id}/?company_id=${companyId}`); } - export async function createINPC( data: INPCCreate, companyId: number -): Promise { - const response = await api.post(`/v1/a76/inpc/?company_id=${companyId}`, data); - return response.data; +): Promise> { + return await api.post(`/v1/a76/inpc/?company_id=${companyId}`, data); } - export async function updateINPC( id: number, data: INPCUpdate, companyId: number -): Promise { - - const response = await api.put(`/v1/a76/inpc/${id}/?company_id=${companyId}`, data); - return response.data; +): Promise> { + return await api.put(`/v1/a76/inpc/${id}/?company_id=${companyId}`, data); } -export async function deleteINPC(id: number, companyId: number): Promise { - await api.delete(`/v1/a76/inpc/${id}/?company_id=${companyId}`); -} \ No newline at end of file +export async function deleteINPC(id: number, companyId: number): Promise> { + return await api.delete(`/v1/a76/inpc/${id}/?company_id=${companyId}`); +} diff --git a/frontend/src/lib/api/dashboard/a76/general_catalogs/locations.ts b/frontend/src/lib/api/dashboard/a76/general_catalogs/locations.ts index 25426fa8..e07612b3 100644 --- a/frontend/src/lib/api/dashboard/a76/general_catalogs/locations.ts +++ b/frontend/src/lib/api/dashboard/a76/general_catalogs/locations.ts @@ -1,69 +1,90 @@ +import type { PaginatedResponse } from '$lib/types'; import { api } from '$lib/api'; -import type { ApiResponse } from '$lib/api'; export interface Location { - id: number; - code: string; - description?: string; - - - tenant_id?: string; - created_at?: string; - updated_at?: string; + id: number; + location_code: string; + location_description: string | null; + company_id: number; + tenant_id: number; } - export interface LocationCreate { - code: string; - description?: string; + location_code: string; + location_description?: string | null; } - export interface LocationUpdate { - code?: string; - description?: string; + location_description?: string | null; } -export interface LocationListResponse { - items: Location[]; - total: number; - page: number; - page_size: number; - pages: number; +export interface LocationListResponse extends PaginatedResponse { + items: Location[]; } - +export interface LocationFilters { + location_code?: string; + location_description?: string; + page?: number; + page_size?: number; +} export async function getLocations( - page: number = 1, - pageSize: number = 50, - filters: Record = {} -): Promise> { - const params = new URLSearchParams({ - page: page.toString(), - page_size: pageSize.toString(), - ...filters - }); + companyId: number, + filters?: LocationFilters +): Promise { + const params = new URLSearchParams({ company_id: companyId.toString() }); - const response = await api.get(`/a24/locations?${params.toString()}`); - return response.data; + if (filters) { + if (filters.location_code) params.append('location_code', filters.location_code); + if (filters.location_description) params.append('location_description', filters.location_description); + if (filters.page) params.append('page', filters.page.toString()); + if (filters.page_size) params.append('page_size', filters.page_size.toString()); + } + + return api.get(`/v1/a76/ports/?${params.toString()}`); } -export async function getLocation(id: number): Promise { - const response = await api.get(`/a24/locations/${id}`); - return response.data; +export async function getLocation( + locationId: number, + companyId: number +): Promise { + const params = new URLSearchParams({ company_id: companyId.toString() }); + return api.get(`/v1/a76/ports/${locationId}?${params.toString()}`); } -export async function createLocation(data: LocationCreate): Promise { - const response = await api.post('/a24/locations', data); - return response.data; +export async function createLocation( + data: LocationCreate, + companyId: number +): Promise { + const params = new URLSearchParams({ company_id: companyId.toString() }); + return api.post(`/v1/a76/ports/?${params.toString()}`, { + port_code: data.location_code, + location_code: data.location_code, + description: null, + location_description: data.location_description || null, + port_type: 'ENTRY' + }); } -export async function updateLocation(id: number, data: LocationUpdate): Promise { - const response = await api.patch(`/a24/locations/${id}`, data); - return response.data; +export async function updateLocation( + locationId: number, + data: LocationUpdate, + companyId: number +): Promise { + const params = new URLSearchParams({ company_id: companyId.toString() }); + return api.put( + `/v1/a76/ports/${locationId}?${params.toString()}`, + { + location_description: data.location_description + } + ); } -export async function deleteLocation(id: number): Promise { - await api.delete(`/a24/locations/${id}`); +export async function deleteLocation( + locationId: number, + companyId: number +): Promise { + const params = new URLSearchParams({ company_id: companyId.toString() }); + return api.delete(`/v1/a76/ports/${locationId}?${params.toString()}`); } \ No newline at end of file diff --git a/frontend/src/lib/api/dashboard/a76/general_catalogs/prevalidators.ts b/frontend/src/lib/api/dashboard/a76/general_catalogs/prevalidators.ts index a7842b43..1041bd8b 100644 --- a/frontend/src/lib/api/dashboard/a76/general_catalogs/prevalidators.ts +++ b/frontend/src/lib/api/dashboard/a76/general_catalogs/prevalidators.ts @@ -5,12 +5,11 @@ export interface Prevalidator { id: number; code: string; description?: string; - // Campos que faltaban según tu modelo Python: customs_prevalidator?: string; patent_prevalidator?: string; - tenant_id: string; - company_id?: string; + tenant_id: number; + company_id: number; created_at: string; updated_at?: string; } @@ -35,33 +34,41 @@ export interface PrevalidatorListResponse { export async function getPrevalidators( page: number = 1, pageSize: number = 50, - filters: Record = {} + filters: Record = {}, + companyId?: number ): Promise> { const params = new URLSearchParams({ page: page.toString(), page_size: pageSize.toString(), ...filters }); + if (companyId) { + params.append('company_id', companyId.toString()); + } - const response = await api.get(`/a76/prevalidators/?${params.toString()}`); + const response = await api.get(`/v1/a76/prevalidators/?${params.toString()}`); return response.data; } -export async function getPrevalidator(id: number): Promise { - const response = await api.get(`/a76/prevalidators/${id}`); +export async function getPrevalidator(id: number, companyId?: number): Promise { + const params = new URLSearchParams(); + if (companyId) { + params.append('company_id', companyId.toString()); + } + const response = await api.get(`/v1/a76/prevalidators/${id}?${params.toString()}`); return response.data; } -export async function createPrevalidator(data: PrevalidatorCreate): Promise { - const response = await api.post('/a76/prevalidators', data); +export async function createPrevalidator(data: PrevalidatorCreate, companyId: number): Promise { + const response = await api.post(`/v1/a76/prevalidators/?company_id=${companyId}`, data); return response.data; } -export async function updatePrevalidator(id: number, data: PrevalidatorUpdate): Promise { - const response = await api.patch(`/a76/prevalidators/${id}`, data); +export async function updatePrevalidator(id: number, data: PrevalidatorUpdate, companyId: number): Promise { + const response = await api.put(`/v1/a76/prevalidators/${id}?company_id=${companyId}`, data); return response.data; } -export async function deletePrevalidator(id: number): Promise { - await api.delete(`/a76/prevalidators/${id}`); -} \ No newline at end of file +export async function deletePrevalidator(id: number, companyId: number): Promise { + await api.delete(`/v1/a76/prevalidators/${id}?company_id=${companyId}`); +} diff --git a/frontend/src/lib/api/dashboard/a76/general_catalogs/signatures.ts b/frontend/src/lib/api/dashboard/a76/general_catalogs/signatures.ts index 1d22184f..3f2bfc7d 100644 --- a/frontend/src/lib/api/dashboard/a76/general_catalogs/signatures.ts +++ b/frontend/src/lib/api/dashboard/a76/general_catalogs/signatures.ts @@ -54,6 +54,7 @@ export async function getSignatures( export async function getSignature(id: number, companyId: number): Promise { const response = await api.get(`/v1/a76/signatures/${id}/?company_id=${companyId}`); + if (response.error) throw new Error(response.error); return response.data; } @@ -63,6 +64,7 @@ export async function createSignature( companyId: number ): Promise { const response = await api.post(`/v1/a76/signatures/?company_id=${companyId}`, data); + if (response.error) throw new Error(response.error); return response.data; } @@ -72,9 +74,11 @@ export async function updateSignature( companyId: number ): Promise { const response = await api.put(`/v1/a76/signatures/${id}/?company_id=${companyId}`, data); + if (response.error) throw new Error(response.error); return response.data; } export async function deleteSignature(id: number, companyId: number): Promise { - await api.delete(`/v1/a76/signatures/${id}/?company_id=${companyId}`); + const response = await api.delete(`/v1/a76/signatures/${id}/?company_id=${companyId}`); + if (response.error) throw new Error(response.error); } \ No newline at end of file diff --git a/frontend/src/lib/api/dashboard/a76/general_catalogs/unit-conversions.ts b/frontend/src/lib/api/dashboard/a76/general_catalogs/unit-conversions.ts index 9a7202ca..7ece8a8e 100644 --- a/frontend/src/lib/api/dashboard/a76/general_catalogs/unit-conversions.ts +++ b/frontend/src/lib/api/dashboard/a76/general_catalogs/unit-conversions.ts @@ -51,6 +51,7 @@ export async function getUnitConversions( export async function getUnitConversion(id: number, companyId: number): Promise { const response = await api.get(`/v1/a76/unit-conversions/${id}/?company_id=${companyId}`); + if (response.error) throw new Error(response.error); return response.data; } @@ -59,6 +60,7 @@ export async function createUnitConversion( companyId: number ): Promise { const response = await api.post(`/v1/a76/unit-conversions/?company_id=${companyId}`, data); + if (response.error) throw new Error(response.error); return response.data; } @@ -69,9 +71,11 @@ export async function updateUnitConversion( companyId: number ): Promise { const response = await api.put(`/v1/a76/unit-conversions/${id}/?company_id=${companyId}`, data); + if (response.error) throw new Error(response.error); return response.data; } export async function deleteUnitConversion(id: number, companyId: number): Promise { - await api.delete(`/v1/a76/unit-conversions/${id}/?company_id=${companyId}`); + const response = await api.delete(`/v1/a76/unit-conversions/${id}/?company_id=${companyId}`); + if (response.error) throw new Error(response.error); } \ No newline at end of file diff --git a/frontend/src/lib/components/dashboard/general_catalogs/doda/columns.ts b/frontend/src/lib/components/dashboard/general_catalogs/doda/columns.ts new file mode 100644 index 00000000..e9751ef3 --- /dev/null +++ b/frontend/src/lib/components/dashboard/general_catalogs/doda/columns.ts @@ -0,0 +1,43 @@ +import type { ColumnDef } from '@tanstack/table-core'; +import type { Doda } from '$lib/api/dashboard/a76/general_catalogs/doda'; +import { renderComponent } from '$lib/components/ui/data-table'; +import DataTableActions from './data-table-actions.svelte'; + +export function createColumns(onSuccess?: () => void): ColumnDef[] { + return [ + { + accessorKey: 'integration_number', + header: 'No. Integración', + cell: ({ row }) => row.original.integration_number || 'N/A' + }, + { + accessorKey: 'patent', + header: 'Patente', + cell: ({ row }) => row.original.patent || 'N/A' + }, + { + accessorKey: 'pedimentos', + header: 'Pedimentos', + cell: ({ row }) => row.original.pedimentos || 'N/A' + }, + { + accessorKey: 'doda_date', + header: 'Fecha', + cell: ({ row }) => row.original.doda_date || 'N/A' + }, + { + accessorKey: 'status', + header: 'Estatus', + cell: ({ row }) => row.original.status || 'N/A' + }, + { + id: 'actions', + cell: ({ row }) => { + return renderComponent(DataTableActions, { + item: row.original, + onSuccess + }); + } + } + ]; +} diff --git a/frontend/src/lib/components/dashboard/general_catalogs/doda/create-edit-dialog.svelte b/frontend/src/lib/components/dashboard/general_catalogs/doda/create-edit-dialog.svelte index d03e173c..47bd0a8e 100644 --- a/frontend/src/lib/components/dashboard/general_catalogs/doda/create-edit-dialog.svelte +++ b/frontend/src/lib/components/dashboard/general_catalogs/doda/create-edit-dialog.svelte @@ -3,99 +3,138 @@ import * as Dialog from "$lib/components/ui/dialog"; import { Input } from "$lib/components/ui/input"; import { Label } from "$lib/components/ui/label"; - import * as Tabs from "$lib/components/ui/tabs"; // Necesario para organizar - import { Plus, Trash2 } from "lucide-svelte"; // Iconos para la lista - + import { Textarea } from "$lib/components/ui/textarea"; + import { Switch } from "$lib/components/ui/switch"; + import * as Tabs from "$lib/components/ui/tabs"; + import { companyStore } from "$lib/stores/company.svelte"; import { createDoda, updateDoda, - type Doda, - type DodaContainer, - type DodaPedimento, - type DodaAmericanPedimento + type Doda } from '$lib/api/dashboard/a76/general_catalogs/doda'; let { open = $bindable(false), - doda = null, + item = null, onSuccess }: { open: boolean; - doda?: Doda | null; + item?: Doda | null; onSuccess?: () => void; } = $props(); - const isEdit = $derived(!!doda); - const title = $derived(isEdit ? `Editar DODA ${doda?.integration_number || ''}` : "Nuevo DODA"); + const isEdit = $derived(!!item); + const title = $derived(isEdit ? `Editar DODA ${item?.integration_number || ''}` : "Nuevo DODA"); // Estado del formulario let formData = $state({ integration_number: '', + doda_date: undefined as number | undefined, + doda_time: undefined as number | undefined, dispatch_customs: '', customs_sections: '', patent: '', + pedimentos: '', caat: '', transport_identification: '', fast_id: '', operation_type: '', - // Arrays - containers: [] as Partial[], - pedimentos_detail: [] as Partial[], - american_pedimentos: [] as Partial[] + selected: false, + user_selected: '', + last_user: '', + responsible: '', + carrier: '', + shipments: '', + pedimento_type: '', + original_chain: '', + serial_number: '', + electronic_signature: '', + transaction_number: '', + status: '', + linq_sat_qr: '', + sat_certificate: '', + sat_digital_seal: '', + xml_doda_sent_path: '', + xml_doda_response_path: '', + sat_original_chain: '', + customs_clearance: undefined as number | undefined, + unique_badge_number: '' }); let loading = $state(false); let error = $state(null); - // Helpers - function formatDate(dateString?: string) { - if (!dateString) return 'N/A'; - return new Date(dateString).toLocaleString('es-MX', { - year: 'numeric', month: 'short', day: 'numeric', - hour: '2-digit', minute: '2-digit' - }); - } - - // Manejo de Arrays - function addContainer() { - formData.containers = [...formData.containers, { container_value: '', seals: '' }]; - } - - function removeContainer(index: number) { - formData.containers = formData.containers.filter((_, i) => i !== index); - } - // Cargar datos $effect(() => { if (open) { - if (doda) { + if (item) { formData = { - integration_number: doda.integration_number || '', - dispatch_customs: doda.dispatch_customs || '', - customs_sections: doda.customs_sections || '', - patent: doda.patent || '', - caat: doda.caat || '', - transport_identification: doda.transport_identification || '', - fast_id: doda.fast_id || '', - operation_type: doda.operation_type || '', - containers: doda.containers || [], - pedimentos_detail: doda.pedimentos_detail || [], - american_pedimentos: doda.american_pedimentos || [] + integration_number: item.integration_number || '', + doda_date: item.doda_date, + doda_time: item.doda_time, + dispatch_customs: item.dispatch_customs || '', + customs_sections: item.customs_sections || '', + patent: item.patent || '', + pedimentos: item.pedimentos || '', + caat: item.caat || '', + transport_identification: item.transport_identification || '', + fast_id: item.fast_id || '', + operation_type: item.operation_type || '', + selected: item.selected || false, + user_selected: item.user_selected || '', + last_user: item.last_user || '', + responsible: item.responsible || '', + carrier: item.carrier || '', + shipments: item.shipments || '', + pedimento_type: item.pedimento_type || '', + original_chain: item.original_chain || '', + serial_number: item.serial_number || '', + electronic_signature: item.electronic_signature || '', + transaction_number: item.transaction_number || '', + status: item.status || '', + linq_sat_qr: item.linq_sat_qr || '', + sat_certificate: item.sat_certificate || '', + sat_digital_seal: item.sat_digital_seal || '', + xml_doda_sent_path: item.xml_doda_sent_path || '', + xml_doda_response_path: item.xml_doda_response_path || '', + sat_original_chain: item.sat_original_chain || '', + customs_clearance: item.customs_clearance, + unique_badge_number: item.unique_badge_number || '' }; } else { // Reset formData = { integration_number: '', + doda_date: undefined, + doda_time: undefined, dispatch_customs: '', customs_sections: '', patent: '', + pedimentos: '', caat: '', transport_identification: '', fast_id: '', operation_type: '', - containers: [], - pedimentos_detail: [], - american_pedimentos: [] + selected: false, + user_selected: '', + last_user: '', + responsible: '', + carrier: '', + shipments: '', + pedimento_type: '', + original_chain: '', + serial_number: '', + electronic_signature: '', + transaction_number: '', + status: '', + linq_sat_qr: '', + sat_certificate: '', + sat_digital_seal: '', + xml_doda_sent_path: '', + xml_doda_response_path: '', + sat_original_chain: '', + customs_clearance: undefined, + unique_badge_number: '' }; } error = null; @@ -103,19 +142,22 @@ }); async function handleSubmit() { + const companyId = companyStore.activeCompany?.id; + if (!companyId) { + error = 'No hay compañía seleccionada'; + return; + } + loading = true; error = null; try { - // Nota: Aquí validaciones si fueran necesarias - - // Fix para el ID: Usamos id o sys_id según venga - const idToUpdate = doda?.sys_id || doda?.id; + const idToUpdate = item?.id; if (isEdit && idToUpdate) { - await updateDoda(idToUpdate, formData); + await updateDoda(idToUpdate, formData, companyId); } else { - await createDoda(formData); + await createDoda(formData, companyId); } open = false; @@ -131,12 +173,12 @@ - + {title} -
{ e.preventDefault(); handleSubmit(); }} class="py-2"> + { e.preventDefault(); handleSubmit(); }} class="py-4"> {#if error}
{error} @@ -144,122 +186,176 @@ {/if} - + General - Transporte - - Contenedores ({formData.containers.length}) - + Aduana/Transp. + SAT / Digital + Otros +
- +
- - + + +
+
+
+
+ +
- - -
-
- - + +
- +
+
+ + +
+
+ + +
+ +
+
+ + +
+
+ + +
+
+ + +
+
- +
- - + + +
+
+
+
+ +
- + +
+
+
+ + +
+
+ + +
+
+ + + +
+
+ + +
+
+ + +
+
+
+ + +
+
+ +