diff --git a/backend/alembic/versions/3a012dff0274_increase_port_description_length.py b/backend/alembic/versions/3a012dff0274_increase_port_description_length.py new file mode 100644 index 00000000..802b6983 --- /dev/null +++ b/backend/alembic/versions/3a012dff0274_increase_port_description_length.py @@ -0,0 +1,28 @@ +"""increase_port_description_length + +Revision ID: 3a012dff0274 +Revises: 7937209f9718 +Create Date: 2025-12-24 10:24:49.927020 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '3a012dff0274' +down_revision: Union[str, Sequence[str], None] = '7937209f9718' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + pass + + +def downgrade() -> None: + """Downgrade schema.""" + pass diff --git a/backend/alembic/versions/531bf8cdae06_create_material_types_table.py b/backend/alembic/versions/531bf8cdae06_create_material_types_table.py index 2179bc81..bfdc01b1 100644 --- a/backend/alembic/versions/531bf8cdae06_create_material_types_table.py +++ b/backend/alembic/versions/531bf8cdae06_create_material_types_table.py @@ -81,6 +81,7 @@ def upgrade() -> None: sa.Column("description", sa.String(length=50), nullable=False), sa.Column("note", sa.String(length=500), nullable=False), sa.Column("type", sa.String(length=15), nullable=False), + sa.Column("operation", sa.String(length=5), nullable=False), sa.PrimaryKeyConstraint("key", name="invoice_types_pkey"), schema="public", ) diff --git a/backend/alembic/versions/7937209f9718_seed_initial_data.py b/backend/alembic/versions/7937209f9718_seed_initial_data.py index 0ce75ac3..864f6c72 100644 --- a/backend/alembic/versions/7937209f9718_seed_initial_data.py +++ b/backend/alembic/versions/7937209f9718_seed_initial_data.py @@ -192,13 +192,13 @@ def upgrade() -> None: values_it = ", ".join( [ - f"('{key}', '{desc.replace(chr(39), chr(39)*2)}', '{note.replace(chr(39), chr(39)*2)}', '{type.replace(chr(39), chr(39)*2)}')" - for key, desc, note, type in invoice_types_seed + f"('{key}', '{desc.replace(chr(39), chr(39)*2)}', '{note.replace(chr(39), chr(39)*2)}', '{type.replace(chr(39), chr(39)*2)}', '{operation.replace(chr(39), chr(39)*2)}')" + for key, desc, note, type, operation in invoice_types_seed ] ) op.execute( f""" - INSERT INTO public.invoice_types (key, description, note, type) VALUES + INSERT INTO public.invoice_types (key, description, note, type, operation) VALUES {values_it} ON CONFLICT (key) DO NOTHING; """ diff --git a/backend/api/v1/common/tenant_crud_routes.py b/backend/api/v1/common/tenant_crud_routes.py index 51a99305..9a3c65ae 100644 --- a/backend/api/v1/common/tenant_crud_routes.py +++ b/backend/api/v1/common/tenant_crud_routes.py @@ -15,7 +15,8 @@ ServiceType = TypeVar("ServiceType") class TenantCRUDRoutes( - Generic[CreateSchemaType, UpdateSchemaType, ResponseSchemaType, ServiceType] + Generic[CreateSchemaType, UpdateSchemaType, + ResponseSchemaType, ServiceType] ): """ Generic CRUD routes factory for tenant-scoped resources @@ -74,7 +75,8 @@ class TenantCRUDRoutes( prefix: str, tags: list[str], resource_name: str = "Resource", - id_name: Optional[str] = None, # For parent resources (e.g., "pedimento_id") + # For parent resources (e.g., "pedimento_id") + id_name: Optional[str] = None, id_type: Type = int, # Type of the ID (int, str, etc.) parent_id_name: Optional[ str @@ -128,9 +130,15 @@ class TenantCRUDRoutes( le=self.max_page_size, description="Page size", ), - status: Optional[str] = Query(None, description="Filter by status"), + status: Optional[str] = Query( + None, description="Filter by status"), + operation_type: Optional[str] = Query( + None, description="Filter by operation type"), + invoice_type: Optional[str] = Query( + None, description="Filter by invoice type"), db: Session = Depends(self.db_dependency), - current_user: Dict[str, Any] = Depends(self.auth_dependency), + current_user: Dict[str, Any] = Depends( + self.auth_dependency), ): tenant_id = validate_access_to_resource( db, company_id, current_user @@ -140,6 +148,10 @@ class TenantCRUDRoutes( filters = {} if status: filters["status"] = status + if operation_type: + filters["operation_type"] = operation_type + if invoice_type: + filters["invoice_type"] = invoice_type items, total = self.service.get_all( db, tenant_id, company_id, skip, page_size, filters @@ -172,7 +184,8 @@ class TenantCRUDRoutes( description="Page size", ), db: Session = Depends(self.db_dependency), - current_user: Dict[str, Any] = Depends(self.auth_dependency), + current_user: Dict[str, Any] = Depends( + self.auth_dependency), ): tenant_id = validate_access_to_resource( db, company_id, current_user @@ -211,7 +224,8 @@ class TenantCRUDRoutes( **path_params, ): - tenant_id = validate_access_to_resource(db, company_id, current_user) + tenant_id = validate_access_to_resource( + db, company_id, current_user) parent_id = path_params.get(self.parent_id_name) # Try method with 4 params (pedimento_id, tenant_id, company_id) @@ -225,7 +239,8 @@ class TenantCRUDRoutes( db, parent_id, tenant_id, company_id ) else: - resource = self.service.get(db, parent_id, tenant_id, company_id) + resource = self.service.get( + db, parent_id, tenant_id, company_id) if not resource: raise HTTPException( @@ -249,7 +264,8 @@ class TenantCRUDRoutes( db: Session = Depends(self.db_dependency), current_user: Dict[str, Any] = Depends(self.auth_dependency), ): - tenant_id = validate_access_to_resource(db, company_id, current_user) + tenant_id = validate_access_to_resource( + db, company_id, current_user) resource = self.service.get_by_id( db, resource_id, tenant_id, company_id @@ -264,10 +280,10 @@ class TenantCRUDRoutes( # POST route if self.parent_id_name: # Child resource - needs parent_id from path - + # Create a closure to capture the schema type create_schema = self.create_schema - + @self.router.post( "/", response_model=self.response_schema, @@ -281,17 +297,18 @@ class TenantCRUDRoutes( db: Session = Depends(self.db_dependency), current_user: Dict[str, Any] = Depends(self.auth_dependency), ): - tenant_id = validate_access_to_resource(db, company_id, current_user) - + tenant_id = validate_access_to_resource( + db, company_id, current_user) + # For child resources, parent_id validation would go here resource = self.service.create(db, data, tenant_id, company_id) return resource else: # Parent resource - no parent_id needed - + # Create a closure to capture the schema type create_schema = self.create_schema - + @self.router.post( "/", response_model=self.response_schema, @@ -305,7 +322,8 @@ class TenantCRUDRoutes( db: Session = Depends(self.db_dependency), current_user: Dict[str, Any] = Depends(self.auth_dependency), ): - tenant_id = validate_access_to_resource(db, company_id, current_user) + tenant_id = validate_access_to_resource( + db, company_id, current_user) resource = self.service.create(db, data, tenant_id, company_id) return resource @@ -314,10 +332,10 @@ class TenantCRUDRoutes( # For child resources: PUT / (parent_id comes from path) if self.parent_id_name: # Child resource - + # Create a closure to capture the schema type update_schema = self.update_schema - + @self.router.put( "/", response_model=self.response_schema, @@ -331,7 +349,8 @@ class TenantCRUDRoutes( current_user: Dict[str, Any] = Depends(self.auth_dependency), **path_params, ): - tenant_id = validate_access_to_resource(db, company_id, current_user) + tenant_id = validate_access_to_resource( + db, company_id, current_user) parent_id = path_params.get(self.parent_id_name) resource = self.service.update( @@ -346,10 +365,10 @@ class TenantCRUDRoutes( else: # Parent resource - + # Create a closure to capture the schema type update_schema = self.update_schema - + @self.router.put( f"/{{{self.id_name}}}", response_model=self.response_schema, @@ -366,7 +385,8 @@ class TenantCRUDRoutes( current_user: Dict[str, Any] = Depends(self.auth_dependency), ): f"""Update {self.resource_name}""" - tenant_id = validate_access_to_resource(db, company_id, current_user) + tenant_id = validate_access_to_resource( + db, company_id, current_user) resource = self.service.update( db, resource_id, tenant_id, data, company_id @@ -395,10 +415,12 @@ class TenantCRUDRoutes( current_user: Dict[str, Any] = Depends(self.auth_dependency), **path_params, ): - tenant_id = validate_access_to_resource(db, company_id, current_user) + tenant_id = validate_access_to_resource( + db, company_id, current_user) parent_id = path_params.get(self.parent_id_name) - success = self.service.delete(db, parent_id, tenant_id, company_id) + success = self.service.delete( + db, parent_id, tenant_id, company_id) if not success: raise HTTPException( @@ -422,9 +444,11 @@ class TenantCRUDRoutes( db: Session = Depends(self.db_dependency), current_user: Dict[str, Any] = Depends(self.auth_dependency), ): - tenant_id = validate_access_to_resource(db, company_id, current_user) + tenant_id = validate_access_to_resource( + db, company_id, current_user) - success = self.service.delete(db, resource_id, tenant_id, company_id) + success = self.service.delete( + db, resource_id, tenant_id, company_id) if not success: raise HTTPException( diff --git a/backend/api/v1/modules/a76/general_catalogs/classification_concepts/models.py b/backend/api/v1/modules/a76/general_catalogs/classification_concepts/models.py index 9789731e..129d05fc 100644 --- a/backend/api/v1/modules/a76/general_catalogs/classification_concepts/models.py +++ b/backend/api/v1/modules/a76/general_catalogs/classification_concepts/models.py @@ -14,5 +14,6 @@ class ClassificationConcept(Base, TenantScopedMixin, TimestampMixin): id: Mapped[int] = mapped_column( Integer, primary_key=True, autoincrement=True) + classification: Mapped[str] = mapped_column( String(30), nullable=False) # CLASIFICACION diff --git a/backend/api/v1/modules/a76/general_catalogs/classification_concepts/service.py b/backend/api/v1/modules/a76/general_catalogs/classification_concepts/service.py index 33245a6d..785403d7 100644 --- a/backend/api/v1/modules/a76/general_catalogs/classification_concepts/service.py +++ b/backend/api/v1/modules/a76/general_catalogs/classification_concepts/service.py @@ -1,10 +1,15 @@ from typing import List, Optional, Tuple, Dict, Any from sqlalchemy.orm import Session from sqlalchemy import select +from sqlalchemy.exc import IntegrityError +from fastapi import HTTPException +import logging from .models import ClassificationConcept from .dto import ClassificationConceptCreate, ClassificationConceptUpdate +logger = logging.getLogger(__name__) + class ClassificationConceptService: @staticmethod @@ -76,6 +81,14 @@ class ClassificationConceptService: if not db_obj: return False - db.delete(db_obj) - db.commit() - return True + try: + db.delete(db_obj) + db.commit() + return True + except IntegrityError as e: + db.rollback() + logger.error(f"Error de integridad al eliminar clasificación de concepto {id}: {str(e)}") + raise HTTPException( + status_code=400, + detail="No se puede eliminar esta clasificación porque tiene registros relacionados (pedimentos, facturas, etc.). Primero debe eliminar o reasignar esos registros." + ) diff --git a/backend/api/v1/modules/a76/general_catalogs/company/routes.py b/backend/api/v1/modules/a76/general_catalogs/company/routes.py index 0095bc4d..294ff490 100644 --- a/backend/api/v1/modules/a76/general_catalogs/company/routes.py +++ b/backend/api/v1/modules/a76/general_catalogs/company/routes.py @@ -2,7 +2,7 @@ Rutas para gestión de empresa """ -from typing import List +from typing import List, Optional from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.orm import Session @@ -17,7 +17,78 @@ from .service import CompanyService # Main router that includes base CRUD router = APIRouter(prefix="/company") -# Custom endpoints +@router.post( + "", # Se suma al prefix, queda POST /api/v1/a76/company + response_model=CompanyResponseDTO, + status_code=status.HTTP_201_CREATED, + summary="Create a new company", +) +async def create_company( + data: CompanyCreateDTO, + db: Session = Depends(get_core_db), + current_user: dict = Depends(get_current_user), +): + + tenant_id = current_user.get("tenant_id") + if not tenant_id: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Tenant ID not found in user data", + ) + + service = CompanyService(db) + return service.create_company_manually(data, tenant_id=tenant_id) + + +@router.get( + "", # GET /api/v1/a76/company with pagination + response_model=dict, + summary="Get companies with pagination", +) +async def list_companies( + page: int = 1, + page_size: int = 50, + name: Optional[str] = None, + rfc: Optional[str] = None, + db: Session = Depends(get_core_db), + current_user: dict = Depends(get_current_user), +): + """Get paginated list of companies for current tenant with optional filters""" + tenant_id = current_user.get("tenant_id") + if not tenant_id: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Tenant ID not found in user data", + ) + + skip = (page - 1) * page_size + filters = {} + if name: + filters["name"] = name + if rfc: + filters["rfc"] = rfc + + service = CompanyService(db) + items, total = service.get_all( + db, + tenant_id, + company_id=0, # Not used for companies + skip=skip, + limit=page_size, + filters=filters if filters else None + ) + + total_pages = (total + page_size - 1) // page_size + + return { + "items": [CompanyResponseDTO.model_validate(item) for item in items], + "total": total, + "page": page, + "page_size": page_size, + "pages": total_pages, + } + + @router.get( "/my-companies", response_model=List[CompanyResponseDTO], @@ -166,17 +237,90 @@ async def get_program_info( "prosec": company.prosec, "prosec_authorization": company.prosec_authorization, } - -# Base CRUD routes using TenantCRUDRoutes -base_router = TenantCRUDRoutes( - service=CompanyService, - create_schema=CompanyCreateDTO, - update_schema=CompanyUpdateDTO, - response_schema=CompanyResponseDTO, - prefix="", - tags=[], - id_name="id", - enable_list=True, - enable_filters=True, -).router -router.include_router(base_router) \ No newline at end of file + + +@router.get( + "/{company_id}", + response_model=CompanyResponseDTO, + summary="Get company by ID", +) +async def get_company( + company_id: int, + db: Session = Depends(get_core_db), + current_user: dict = Depends(get_current_user), +): + """Get a specific company by ID""" + tenant_id = current_user.get("tenant_id") + if not tenant_id: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Tenant ID not found in user data", + ) + + company = CompanyService.get_by_id(db, company_id, tenant_id, 0) + if not company: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Company not found", + ) + + return CompanyResponseDTO.model_validate(company) + + +@router.put( + "/{company_id}", + response_model=CompanyResponseDTO, + summary="Update company", +) +async def update_company( + company_id: int, + data: CompanyUpdateDTO, + db: Session = Depends(get_core_db), + current_user: dict = Depends(get_current_user), +): + """Update a company""" + tenant_id = current_user.get("tenant_id") + if not tenant_id: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Tenant ID not found in user data", + ) + + updated_company = CompanyService.update( + db, company_id, tenant_id, 0, data + ) + if not updated_company: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Company not found", + ) + + return CompanyResponseDTO.model_validate(updated_company) + + +@router.delete( + "/{company_id}", + status_code=status.HTTP_204_NO_CONTENT, + summary="Delete company", +) +async def delete_company( + company_id: int, + db: Session = Depends(get_core_db), + current_user: dict = Depends(get_current_user), +): + """Delete a company""" + tenant_id = current_user.get("tenant_id") + if not tenant_id: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Tenant ID not found in user data", + ) + + success = CompanyService.delete(db, company_id, tenant_id, 0) + if not success: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Company not found", + ) + + return None \ No newline at end of file diff --git a/backend/api/v1/modules/a76/general_catalogs/company/service.py b/backend/api/v1/modules/a76/general_catalogs/company/service.py index a42250a5..63835cd1 100644 --- a/backend/api/v1/modules/a76/general_catalogs/company/service.py +++ b/backend/api/v1/modules/a76/general_catalogs/company/service.py @@ -64,6 +64,7 @@ class CompanyService: .first() ) + # ESTE ES EL MÉTODO VIEJO QUE CAUSABA PROBLEMAS (Lo dejamos por si acaso) @staticmethod def create( db: Session, @@ -71,7 +72,7 @@ class CompanyService: tenant_id: int, company_id: int, ) -> Company: - """Create a new company""" + """Create a new company (MÉTODO GENÉRICO - NO USAR PARA CREACIÓN MANUAL)""" try: db_company = Company( **company_data.model_dump(exclude_unset=True), @@ -136,10 +137,20 @@ class CompanyService: db.delete(company) db.commit() return True + except IntegrityError as e: + db.rollback() + logger.error(f"IntegrityError deleting company {company_id}: {str(e)}") + # Check if it's a foreign key constraint + if "foreign key constraint" in str(e).lower(): + raise HTTPException( + status_code=400, + detail="No se puede eliminar la empresa porque tiene registros relacionados (facturas, conceptos, etc.)" + ) + raise HTTPException(status_code=400, detail="Error al eliminar la empresa") except Exception as e: db.rollback() logger.error(f"Error deleting company {company_id}: {str(e)}") - raise HTTPException(status_code=500, detail="Error deleting company") + raise HTTPException(status_code=500, detail="Error al eliminar la empresa") # Custom methods def get_companies_by_tenant(self, tenant_id: int) -> List[Company]: @@ -159,3 +170,31 @@ class CompanyService: .first() is not None ) + + def create_company_manually(self, data: CompanyCreateDTO, tenant_id: int) -> Company: + + try: + # 1. Preparar datos + obj_data = data.model_dump(exclude_unset=True) + + # 2. Crear objeto SQLAlchemy + db_obj = Company(**obj_data, tenant_id=tenant_id) + + # 3. Guardar + self.db.add(db_obj) + self.db.commit() + self.db.refresh(db_obj) + + return db_obj + + except IntegrityError as e: + self.db.rollback() + logger.error(f"IntegrityError creating company manually: {str(e)}") + raise HTTPException( + status_code=400, + detail="Error de integridad: Es posible que esta empresa ya exista.", + ) + except Exception as e: + self.db.rollback() + logger.error(f"Error creating company manually: {str(e)}") + raise HTTPException(status_code=500, detail=f"Error creando empresa: {str(e)}") \ No newline at end of file diff --git a/backend/api/v1/modules/a76/general_catalogs/concepts/dto.py b/backend/api/v1/modules/a76/general_catalogs/concepts/dto.py index e4b391ab..e8c030e0 100644 --- a/backend/api/v1/modules/a76/general_catalogs/concepts/dto.py +++ b/backend/api/v1/modules/a76/general_catalogs/concepts/dto.py @@ -4,6 +4,7 @@ from pydantic import BaseModel, Field, ConfigDict class ConceptBase(BaseModel): code: str = Field(..., max_length=15, description="Concept Code (CLAVE)") + company_id: int = Field(..., description="Company ID") description: Optional[str] = Field( None, max_length=120, description="Description") description_en: Optional[str] = Field( @@ -18,7 +19,6 @@ class ConceptBase(BaseModel): section: Optional[int] = Field(None, description="Section") classification: Optional[str] = Field( None, max_length=30, description="Classification") - company_id: int = Field(..., description="Company ID") class ConceptCreate(ConceptBase): @@ -32,7 +32,7 @@ class ConceptUpdate(BaseModel): detailed_description: Optional[str] = Field(None, max_length=1000) priority: Optional[int] = None priority_ame: Optional[int] = None - first_total: Optional[bool] = None + first_total: Optional[bool] = None type: Optional[str] = Field(None, max_length=9) is_printed: Optional[bool] = None section: Optional[int] = None diff --git a/backend/api/v1/modules/a76/general_catalogs/concepts/models.py b/backend/api/v1/modules/a76/general_catalogs/concepts/models.py index 8c13fc34..d3824d1d 100644 --- a/backend/api/v1/modules/a76/general_catalogs/concepts/models.py +++ b/backend/api/v1/modules/a76/general_catalogs/concepts/models.py @@ -18,25 +18,39 @@ class Concept(Base, TenantScopedMixin, TimestampMixin): id: Mapped[int] = mapped_column( Integer, primary_key=True, autoincrement=True) + + company_id: Mapped[int] = mapped_column( + Integer, nullable=False) # IDEMPRESA + code: Mapped[str] = mapped_column(String(15), nullable=False) # CLAVE + description: Mapped[Optional[str]] = mapped_column( String(120), nullable=True) # DESCRIPCION + description_en: Mapped[Optional[str]] = mapped_column( String(120), nullable=True) # DESCRIPCIONINGLES + detailed_description: Mapped[Optional[str]] = mapped_column( String(1000), nullable=True) # DESCRIPCIONDETALLADA + priority: Mapped[Optional[int]] = mapped_column( Integer, nullable=True) # PRIORIDAD + priority_ame: Mapped[Optional[int]] = mapped_column( Integer, nullable=True) # PRIORIDADAME + first_total: Mapped[Optional[bool]] = mapped_column( Boolean, nullable=True) # PRIMERTOTAL + type: Mapped[Optional[str]] = mapped_column( String(9), nullable=True) # TIPO + is_printed: Mapped[Optional[bool]] = mapped_column( Boolean, nullable=True) # SEIMPRIME + section: Mapped[Optional[int]] = mapped_column( Integer, nullable=True) # SECCION + classification: Mapped[Optional[str]] = mapped_column(String(30), ForeignKey( "a76.classification_concepts.classification"), nullable=True) # CLASIFICACION diff --git a/backend/api/v1/modules/a76/general_catalogs/concepts/service.py b/backend/api/v1/modules/a76/general_catalogs/concepts/service.py index bba0de63..b0117771 100644 --- a/backend/api/v1/modules/a76/general_catalogs/concepts/service.py +++ b/backend/api/v1/modules/a76/general_catalogs/concepts/service.py @@ -1,10 +1,16 @@ from typing import List, Optional, Tuple, Dict, Any +import logging + from sqlalchemy.orm import Session from sqlalchemy import select +from sqlalchemy.exc import IntegrityError +from fastapi import HTTPException from .models import Concept from .dto import ConceptCreate, ConceptUpdate +logger = logging.getLogger(__name__) + class ConceptService: @staticmethod @@ -74,6 +80,20 @@ class ConceptService: if not db_obj: return False - db.delete(db_obj) - db.commit() - return True + try: + db.delete(db_obj) + db.commit() + return True + except IntegrityError as e: + db.rollback() + logger.error(f"IntegrityError deleting concept {id}: {str(e)}") + if "foreign key constraint" in str(e).lower(): + raise HTTPException( + status_code=400, + detail="No se puede eliminar el concepto porque tiene registros relacionados" + ) + raise HTTPException(status_code=400, detail="Error al eliminar el concepto") + except Exception as e: + db.rollback() + logger.error(f"Error deleting concept {id}: {str(e)}") + raise HTTPException(status_code=500, detail="Error al eliminar el concepto") diff --git a/backend/api/v1/modules/a76/general_catalogs/customs_broker_concepts/dto.py b/backend/api/v1/modules/a76/general_catalogs/customs_broker_concepts/dto.py index 470de77a..30416dea 100644 --- a/backend/api/v1/modules/a76/general_catalogs/customs_broker_concepts/dto.py +++ b/backend/api/v1/modules/a76/general_catalogs/customs_broker_concepts/dto.py @@ -4,8 +4,7 @@ from pydantic import BaseModel, Field, ConfigDict class CustomsBrokerConceptBase(BaseModel): - broker_key: str = Field(..., max_length=5, - description="Customs Broker Key (CLAVEAA)") + broker_key: str = Field(..., max_length=5, description="Customs Broker Key (CLAVEAA)") concept: str = Field(..., max_length=15, description="Concept") amount: Optional[Decimal] = Field(None, description="Amount") priority: Optional[int] = Field(None, description="Priority") diff --git a/backend/api/v1/modules/a76/general_catalogs/customs_broker_concepts/models.py b/backend/api/v1/modules/a76/general_catalogs/customs_broker_concepts/models.py index f55c97c4..301e8d48 100644 --- a/backend/api/v1/modules/a76/general_catalogs/customs_broker_concepts/models.py +++ b/backend/api/v1/modules/a76/general_catalogs/customs_broker_concepts/models.py @@ -9,17 +9,23 @@ from core.database import Base class CustomsBrokerConcept(Base, TenantScopedMixin, TimestampMixin): __tablename__ = "customs_broker_concepts" __table_args__ = ( - UniqueConstraint("broker_key", "concept", name="uq_broker_concept"), + UniqueConstraint("broker_key", "concept", "company_id", name="uq_broker_concept"), {"schema": "a76"} ) id: Mapped[int] = mapped_column( Integer, primary_key=True, autoincrement=True) + + company_id: Mapped[int] = mapped_column(Integer, nullable=False) + broker_key: Mapped[str] = mapped_column( String(5), nullable=False) # CLAVEAA + concept: Mapped[str] = mapped_column( String(15), nullable=False) # CONCEPTO + amount: Mapped[Optional[Decimal]] = mapped_column( Numeric(11, 2), nullable=True) # IMPORTE + priority: Mapped[Optional[int]] = mapped_column( Integer, nullable=True) # PRIORIDAD diff --git a/backend/api/v1/modules/a76/general_catalogs/customs_broker_concepts/routes.py b/backend/api/v1/modules/a76/general_catalogs/customs_broker_concepts/routes.py index eeb5bfab..c8ec83ad 100644 --- a/backend/api/v1/modules/a76/general_catalogs/customs_broker_concepts/routes.py +++ b/backend/api/v1/modules/a76/general_catalogs/customs_broker_concepts/routes.py @@ -7,7 +7,7 @@ router = TenantCRUDRoutes( create_schema=CustomsBrokerConceptCreate, update_schema=CustomsBrokerConceptUpdate, response_schema=CustomsBrokerConceptResponse, - prefix="/customs-broker-concepts", + prefix="/customs-broker-concepts", tags=["a76.general_catalogs.customs_broker_concepts"], resource_name="Customs Broker Concept", enable_list=True, diff --git a/backend/api/v1/modules/a76/general_catalogs/customs_broker_concepts/service.py b/backend/api/v1/modules/a76/general_catalogs/customs_broker_concepts/service.py index 43e7d1d8..643981e4 100644 --- a/backend/api/v1/modules/a76/general_catalogs/customs_broker_concepts/service.py +++ b/backend/api/v1/modules/a76/general_catalogs/customs_broker_concepts/service.py @@ -1,10 +1,16 @@ from typing import List, Optional, Tuple, Dict, Any +import logging + from sqlalchemy.orm import Session from sqlalchemy import select +from sqlalchemy.exc import IntegrityError +from fastapi import HTTPException from .models import CustomsBrokerConcept from .dto import CustomsBrokerConceptCreate, CustomsBrokerConceptUpdate +logger = logging.getLogger(__name__) + class CustomsBrokerConceptService: @staticmethod @@ -76,6 +82,20 @@ class CustomsBrokerConceptService: if not db_obj: return False - db.delete(db_obj) - db.commit() - return True + try: + db.delete(db_obj) + db.commit() + return True + except IntegrityError as e: + db.rollback() + logger.error(f"IntegrityError deleting customs broker concept {id}: {str(e)}") + if "foreign key constraint" in str(e).lower(): + raise HTTPException( + status_code=400, + detail="No se puede eliminar el concepto porque tiene registros relacionados" + ) + raise HTTPException(status_code=400, detail="Error al eliminar el concepto") + except Exception as e: + db.rollback() + logger.error(f"Error deleting customs broker concept {id}: {str(e)}") + raise HTTPException(status_code=500, detail="Error al eliminar el concepto") 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/doda/models.py b/backend/api/v1/modules/a76/general_catalogs/doda/models.py index e61b2918..810d4977 100644 --- a/backend/api/v1/modules/a76/general_catalogs/doda/models.py +++ b/backend/api/v1/modules/a76/general_catalogs/doda/models.py @@ -151,19 +151,19 @@ class DodaContainerSeal(Base, TenantScopedMixin, TimestampMixin): {"schema": "a76"}, ) - # Primary key + id: Mapped[int] = mapped_column( Integer, primary_key=True, autoincrement=True) - # Foreign key and line info + container_id: Mapped[int] = mapped_column(Integer, nullable=False) doda_id: Mapped[int] = mapped_column(Integer, nullable=False) seal_line: Mapped[int] = mapped_column(Integer, nullable=False) - # Seal information + seal_value: Mapped[Optional[str]] = mapped_column(String(21)) - # Relationships + container: Mapped["DodaContainer"] = relationship( "DodaContainer", back_populates="seals_detail" ) diff --git a/backend/api/v1/modules/a76/general_catalogs/doda/service.py b/backend/api/v1/modules/a76/general_catalogs/doda/service.py index 43690982..cccb1314 100644 --- a/backend/api/v1/modules/a76/general_catalogs/doda/service.py +++ b/backend/api/v1/modules/a76/general_catalogs/doda/service.py @@ -131,14 +131,21 @@ class DodaService: db: Session, id: int, tenant_id: int, company_id: int ) -> bool: """Delete a DODA""" - try: - db_doda = DodaService.get_by_id(db, id, tenant_id, company_id) - if not db_doda: - return False + db_doda = DodaService.get_by_id(db, id, tenant_id, company_id) + if not db_doda: + return False + try: db.delete(db_doda) db.commit() return True + except IntegrityError as e: + db.rollback() + logger.error(f"Error de integridad al eliminar DODA {id}: {str(e)}") + raise HTTPException( + status_code=400, + detail="No se puede eliminar este DODA porque tiene registros relacionados. Primero debe eliminar o reasignar esos registros." + ) except Exception as e: db.rollback() logger.error(f"Error deleting DODA: {str(e)}") 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 7bcc724a..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) @@ -39,11 +41,15 @@ class EquivalencyItem(Base, TenantScopedMixin, TimestampMixin): id: Mapped[int] = mapped_column( Integer, primary_key=True, autoincrement=True) + equivalency_id: Mapped[int] = mapped_column( Integer, ForeignKey("a76.equivalencies.id"), nullable=False) + original_field: Mapped[str] = mapped_column( String(100), nullable=False) # Relation to Unit of Measure + external_field: Mapped[str] = mapped_column(String(100), nullable=False) equivalency: Mapped["Equivalency"] = relationship(back_populates="items") + unit_of_measure: Mapped["UnitOfMeasure"] = relationship() 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/error_catalogs/models.py b/backend/api/v1/modules/a76/general_catalogs/error_catalogs/models.py index af8d053d..147770c3 100644 --- a/backend/api/v1/modules/a76/general_catalogs/error_catalogs/models.py +++ b/backend/api/v1/modules/a76/general_catalogs/error_catalogs/models.py @@ -24,7 +24,8 @@ class ErrorClassification(Base, TenantScopedMixin, TimestampMixin): # Primary key id: Mapped[int] = mapped_column( - Integer, primary_key=True, autoincrement=True) + Integer, primary_key=True, autoincrement=True + ) # Classification code (unique) code: Mapped[str] = mapped_column(String(100), nullable=False, unique=True) @@ -60,7 +61,8 @@ class ErrorCatalog(Base, TenantScopedMixin, TimestampMixin): # Primary key id: Mapped[int] = mapped_column( - Integer, primary_key=True, autoincrement=True) + Integer, primary_key=True, autoincrement=True + ) # Error code (unique) code: Mapped[str] = mapped_column(String(15), nullable=False, unique=True) @@ -77,4 +79,4 @@ class ErrorCatalog(Base, TenantScopedMixin, TimestampMixin): ) def __repr__(self): - return f"" + return f"" \ No newline at end of file diff --git a/backend/api/v1/modules/a76/general_catalogs/exchange_rate/models.py b/backend/api/v1/modules/a76/general_catalogs/exchange_rate/models.py index 5e9d95b4..92f8e4f7 100644 --- a/backend/api/v1/modules/a76/general_catalogs/exchange_rate/models.py +++ b/backend/api/v1/modules/a76/general_catalogs/exchange_rate/models.py @@ -30,6 +30,9 @@ class ExchangeRate(Base, TenantScopedMixin, TimestampMixin): id: Mapped[int] = mapped_column(Integer, primary_key=True) date: Mapped[datetime] = mapped_column(DateTime) + value: Mapped[Optional[Decimal]] = mapped_column(DECIMAL(13, 6)) + local_currency: Mapped[Optional[str]] = mapped_column(String(7)) + foreign_currency: Mapped[Optional[str]] = mapped_column(String(7)) diff --git a/backend/api/v1/modules/a76/general_catalogs/identifiers/dto.py b/backend/api/v1/modules/a76/general_catalogs/identifiers/dto.py index 718da777..9432a023 100644 --- a/backend/api/v1/modules/a76/general_catalogs/identifiers/dto.py +++ b/backend/api/v1/modules/a76/general_catalogs/identifiers/dto.py @@ -1,9 +1,6 @@ from typing import Optional from pydantic import BaseModel, Field, ConfigDict -# Identifier DTOs - - class IdentifierBase(BaseModel): code: str = Field(..., max_length=2, description="Identifier Code (CLAVE)") description: Optional[str] = Field( @@ -11,28 +8,23 @@ class IdentifierBase(BaseModel): level: Optional[str] = Field(None, max_length=1, description="Level") complement: Optional[str] = Field( None, max_length=5000, description="Complement") - company_id: int = Field(..., description="Company ID") - class IdentifierCreate(IdentifierBase): pass - class IdentifierUpdate(BaseModel): code: Optional[str] = Field(None, max_length=2) description: Optional[str] = Field(None, max_length=1000) level: Optional[str] = Field(None, max_length=1) complement: Optional[str] = Field(None, max_length=5000) - class IdentifierResponse(IdentifierBase): id: int tenant_id: int + company_id: int model_config = ConfigDict(from_attributes=True) -# Identifier Detail DTOs - class IdentifierDetailBase(BaseModel): invoice_consecutive: Optional[int] = Field( @@ -47,13 +39,10 @@ class IdentifierDetailBase(BaseModel): None, max_length=51, description="Complement 2") complement3: Optional[str] = Field( None, max_length=50, description="Complement 3") - company_id: int = Field(..., description="Company ID") - class IdentifierDetailCreate(IdentifierDetailBase): pass - class IdentifierDetailUpdate(BaseModel): invoice_consecutive: Optional[int] = None part_line: Optional[int] = None @@ -63,9 +52,9 @@ class IdentifierDetailUpdate(BaseModel): complement2: Optional[str] = Field(None, max_length=51) complement3: Optional[str] = Field(None, max_length=50) - class IdentifierDetailResponse(IdentifierDetailBase): id: int tenant_id: int + company_id: int - model_config = ConfigDict(from_attributes=True) + model_config = ConfigDict(from_attributes=True) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/general_catalogs/identifiers/models.py b/backend/api/v1/modules/a76/general_catalogs/identifiers/models.py index 27174064..daabe759 100644 --- a/backend/api/v1/modules/a76/general_catalogs/identifiers/models.py +++ b/backend/api/v1/modules/a76/general_catalogs/identifiers/models.py @@ -14,11 +14,15 @@ class Identifier(Base, TenantScopedMixin, TimestampMixin): id: Mapped[int] = mapped_column( Integer, primary_key=True, autoincrement=True) + code: Mapped[str] = mapped_column(String(2), nullable=False) # CLAVE + description: Mapped[Optional[str]] = mapped_column( String(1000), nullable=True) # DESCRIPCION + level: Mapped[Optional[str]] = mapped_column( String(1), nullable=True) # NIVEL + complement: Mapped[Optional[str]] = mapped_column( String(5000), nullable=True) # COMPLEMENTO 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/dto.py b/backend/api/v1/modules/a76/general_catalogs/inpc/dto.py index 7718a927..72afc5c6 100644 --- a/backend/api/v1/modules/a76/general_catalogs/inpc/dto.py +++ b/backend/api/v1/modules/a76/general_catalogs/inpc/dto.py @@ -2,7 +2,6 @@ from typing import Optional from decimal import Decimal from pydantic import BaseModel, Field, ConfigDict - class INPCBase(BaseModel): year: str = Field(..., max_length=4, description="Year (YYYY)") month: str = Field(..., max_length=2, description="Month (MM)") @@ -12,7 +11,6 @@ class INPCBase(BaseModel): class INPCCreate(INPCBase): pass - class INPCUpdate(BaseModel): year: Optional[str] = Field(None, max_length=4) month: Optional[str] = Field(None, max_length=2) @@ -21,5 +19,7 @@ class INPCUpdate(BaseModel): class INPCResponse(INPCBase): id: int + tenant_id: int + company_id: int - model_config = ConfigDict(from_attributes=True) + model_config = ConfigDict(from_attributes=True) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/general_catalogs/inpc/models.py b/backend/api/v1/modules/a76/general_catalogs/inpc/models.py index 7a41d26b..65e79049 100644 --- a/backend/api/v1/modules/a76/general_catalogs/inpc/models.py +++ b/backend/api/v1/modules/a76/general_catalogs/inpc/models.py @@ -16,7 +16,10 @@ class INPC(Base, TenantScopedMixin, TimestampMixin): id: Mapped[int] = mapped_column( Integer, primary_key=True, autoincrement=True) + year: Mapped[str] = mapped_column(String(4), nullable=False) # ANIO + month: Mapped[str] = mapped_column(String(2), nullable=False) # MES + value: Mapped[Optional[Decimal]] = mapped_column( Numeric(19, 8), nullable=True) # VALOR diff --git a/backend/api/v1/modules/a76/general_catalogs/inpc/routes.py b/backend/api/v1/modules/a76/general_catalogs/inpc/routes.py index dd2b6cdb..a91b276d 100644 --- a/backend/api/v1/modules/a76/general_catalogs/inpc/routes.py +++ b/backend/api/v1/modules/a76/general_catalogs/inpc/routes.py @@ -1,20 +1,16 @@ -from fastapi import APIRouter from api.v1.common.tenant_crud_routes import TenantCRUDRoutes from .models import INPC from .dto import INPCCreate, INPCResponse, INPCUpdate from .service import INPCService -router = APIRouter(prefix="/inpc", tags=["a76.general_catalogs.inpc"]) - -inpc_crud = TenantCRUDRoutes( +# Usamos TenantCRUDRoutes directamente +router = TenantCRUDRoutes( service=INPCService, create_schema=INPCCreate, update_schema=INPCUpdate, response_schema=INPCResponse, - prefix="", - tags=["INPC"], + prefix="/inpc", + tags=["a76.general_catalogs.inpc"], resource_name="INPC", enable_list=True, -) - -router.include_router(inpc_crud.router) +).router \ No newline at end of file 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/legends/dto.py b/backend/api/v1/modules/a76/general_catalogs/legends/dto.py index c3900566..2f3b9570 100644 --- a/backend/api/v1/modules/a76/general_catalogs/legends/dto.py +++ b/backend/api/v1/modules/a76/general_catalogs/legends/dto.py @@ -21,3 +21,5 @@ class LegendResponse(LegendBase): id: int model_config = ConfigDict(from_attributes=True) + tenant_id : int + company_id : int diff --git a/backend/api/v1/modules/a76/general_catalogs/legends/models.py b/backend/api/v1/modules/a76/general_catalogs/legends/models.py index eaea60a1..f6ea4591 100644 --- a/backend/api/v1/modules/a76/general_catalogs/legends/models.py +++ b/backend/api/v1/modules/a76/general_catalogs/legends/models.py @@ -15,6 +15,8 @@ class Legend(Base, TenantScopedMixin, TimestampMixin): id: Mapped[int] = mapped_column( Integer, primary_key=True, autoincrement=True) + code: Mapped[int] = mapped_column(Integer, nullable=False) # CLAVELEY + description: Mapped[Optional[str]] = mapped_column( String(2000), nullable=True) # DESCLEYENDA diff --git a/backend/api/v1/modules/a76/general_catalogs/legends/routes.py b/backend/api/v1/modules/a76/general_catalogs/legends/routes.py index e053c4d5..69772309 100644 --- a/backend/api/v1/modules/a76/general_catalogs/legends/routes.py +++ b/backend/api/v1/modules/a76/general_catalogs/legends/routes.py @@ -4,17 +4,13 @@ from .models import Legend from .dto import LegendCreate, LegendResponse, LegendUpdate from .service import LegendService -router = APIRouter(prefix="/legends", tags=["a76.general_catalogs.legends"]) - -legend_crud = TenantCRUDRoutes( +router = TenantCRUDRoutes( service=LegendService, create_schema=LegendCreate, update_schema=LegendUpdate, response_schema=LegendResponse, - prefix="", - tags=["Legends"], + prefix="/legends", + tags=["a76.general_catalogs.legends"], resource_name="Legend", enable_list=True, -) - -router.include_router(legend_crud.router) +).router diff --git a/backend/api/v1/modules/a76/general_catalogs/legends/service.py b/backend/api/v1/modules/a76/general_catalogs/legends/service.py index 0221ecbb..0351f16a 100644 --- a/backend/api/v1/modules/a76/general_catalogs/legends/service.py +++ b/backend/api/v1/modules/a76/general_catalogs/legends/service.py @@ -1,10 +1,15 @@ from typing import List, Optional, Tuple, Dict, Any from sqlalchemy.orm import Session from sqlalchemy import select +from sqlalchemy.exc import IntegrityError +from fastapi import HTTPException +import logging from .models import Legend from .dto import LegendCreate, LegendUpdate +logger = logging.getLogger(__name__) + class LegendService: @staticmethod @@ -63,8 +68,8 @@ class LegendService: def update( db: Session, id: int, - data: LegendUpdate, tenant_id: int, + data: LegendUpdate, company_id: int ) -> Optional[Legend]: db_obj = LegendService.get_by_id(db, id, tenant_id, company_id) @@ -90,6 +95,14 @@ class LegendService: if not db_obj: return False - db.delete(db_obj) - db.commit() - return True + try: + db.delete(db_obj) + db.commit() + return True + except IntegrityError as e: + db.rollback() + logger.error(f"Error de integridad al eliminar leyenda {id}: {str(e)}") + raise HTTPException( + status_code=400, + detail="No se puede eliminar esta leyenda porque tiene registros relacionados (pedimentos, facturas, etc.). Primero debe eliminar o reasignar esos registros." + ) 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/multi_currency_types/dto.py b/backend/api/v1/modules/a76/general_catalogs/multi_currency_types/dto.py index 6bbdfe9c..b76b798f 100644 --- a/backend/api/v1/modules/a76/general_catalogs/multi_currency_types/dto.py +++ b/backend/api/v1/modules/a76/general_catalogs/multi_currency_types/dto.py @@ -27,5 +27,7 @@ class MultiCurrencyTypeUpdate(BaseModel): class MultiCurrencyTypeResponse(MultiCurrencyTypeBase): id: int - + company_id: int + tenant_id: int + model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a76/general_catalogs/multi_currency_types/models.py b/backend/api/v1/modules/a76/general_catalogs/multi_currency_types/models.py index 91751a9a..e119b8b8 100644 --- a/backend/api/v1/modules/a76/general_catalogs/multi_currency_types/models.py +++ b/backend/api/v1/modules/a76/general_catalogs/multi_currency_types/models.py @@ -18,13 +18,18 @@ class MultiCurrencyType(Base, TenantScopedMixin, TimestampMixin): id: Mapped[int] = mapped_column( Integer, primary_key=True, autoincrement=True) + currency_type_code: Mapped[str] = mapped_column( String(3), ForeignKey("public.currency_types.code"), nullable=False) + country_key: Mapped[Optional[str]] = mapped_column( String(3), ForeignKey("public.countries.m3_key"), nullable=True) + conversion_factor: Mapped[Optional[Decimal]] = mapped_column( Numeric(13, 6), nullable=True) + publication_date: Mapped[int] = mapped_column(Integer, nullable=False) currency_type: Mapped["CurrencyType"] = relationship() + country: Mapped[Optional["Country"]] = relationship() diff --git a/backend/api/v1/modules/a76/general_catalogs/packages/models.py b/backend/api/v1/modules/a76/general_catalogs/packages/models.py index 98e560d3..f191fa94 100644 --- a/backend/api/v1/modules/a76/general_catalogs/packages/models.py +++ b/backend/api/v1/modules/a76/general_catalogs/packages/models.py @@ -31,4 +31,4 @@ class Package(Base, TenantScopedMixin, TimestampMixin): plurals: Mapped[Optional[str]] = mapped_column(String(4)) plural_in: Mapped[Optional[str]] = mapped_column(String(4)) code_ace: Mapped[Optional[str]] = mapped_column(String(4)) - code_aamex: Mapped[Optional[str]] = mapped_column(String(9)) + code_aamex: Mapped[Optional[str]] = mapped_column(String(9)) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/general_catalogs/packages/services.py b/backend/api/v1/modules/a76/general_catalogs/packages/services.py index be52f4f7..cea4d882 100644 --- a/backend/api/v1/modules/a76/general_catalogs/packages/services.py +++ b/backend/api/v1/modules/a76/general_catalogs/packages/services.py @@ -3,11 +3,16 @@ Service layer for Packages (GBultos). """ from typing import Optional, Tuple, List, Dict, Any +import logging from sqlalchemy.orm import Session +from sqlalchemy.exc import IntegrityError +from fastapi import HTTPException from . import dto, models +logger = logging.getLogger(__name__) + class PackageService: """Service for Package CRUD operations with tenant support""" @@ -109,6 +114,20 @@ class PackageService: if not package: return False - db.delete(package) - db.commit() - return True + try: + db.delete(package) + db.commit() + return True + except IntegrityError as e: + db.rollback() + logger.error(f"IntegrityError deleting package {package_id}: {str(e)}") + if "foreign key constraint" in str(e).lower(): + raise HTTPException( + status_code=400, + detail="No se puede eliminar el bulto porque tiene registros relacionados" + ) + raise HTTPException(status_code=400, detail="Error al eliminar el bulto") + except Exception as e: + db.rollback() + logger.error(f"Error deleting package {package_id}: {str(e)}") + raise HTTPException(status_code=500, detail="Error al eliminar el bulto") diff --git a/backend/api/v1/modules/a76/general_catalogs/ports/service.py b/backend/api/v1/modules/a76/general_catalogs/ports/service.py index 54b41df2..4d4d9cc2 100644 --- a/backend/api/v1/modules/a76/general_catalogs/ports/service.py +++ b/backend/api/v1/modules/a76/general_catalogs/ports/service.py @@ -63,8 +63,8 @@ class PortService: def update( db: Session, id: int, - data: PortUpdate, tenant_id: int, + data: PortUpdate, company_id: int ) -> Optional[Port]: db_obj = PortService.get_by_id(db, id, tenant_id, company_id) 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/models.py b/backend/api/v1/modules/a76/general_catalogs/prevalidators/models.py index aab8dc00..a9d0f2b8 100644 --- a/backend/api/v1/modules/a76/general_catalogs/prevalidators/models.py +++ b/backend/api/v1/modules/a76/general_catalogs/prevalidators/models.py @@ -31,7 +31,9 @@ class Prevalidator(Base, TenantScopedMixin, TimestampMixin): # Prevalidator information customs_prevalidator: Mapped[Optional[str]] = mapped_column(String(20)) + patent_prevalidator: Mapped[Optional[str]] = mapped_column(String(20)) + description: Mapped[Optional[str]] = mapped_column(String(50)) def __repr__(self): 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 4524f91c..fc317714 100644 --- a/backend/api/v1/modules/a76/general_catalogs/signatures/dto.py +++ b/backend/api/v1/modules/a76/general_catalogs/signatures/dto.py @@ -11,6 +11,7 @@ class SignatureCreateDTO(BaseModel): """DTO para crear una firma""" code: str = Field(..., max_length=10, description="Signature code") + signature: Optional[str] = Field( None, max_length=1000, description="Signature") photo_path: Optional[str] = Field( @@ -39,6 +40,8 @@ class SignatureResponseDTO(BaseModel): code: str signature: Optional[str] = None photo_path: Optional[str] = None + tenant_id: int + company_id: int class Config: from_attributes = True diff --git a/backend/api/v1/modules/a76/general_catalogs/signatures/models.py b/backend/api/v1/modules/a76/general_catalogs/signatures/models.py index e3d04b63..0885a276 100644 --- a/backend/api/v1/modules/a76/general_catalogs/signatures/models.py +++ b/backend/api/v1/modules/a76/general_catalogs/signatures/models.py @@ -32,6 +32,7 @@ class Signature(Base, TenantScopedMixin, TimestampMixin): # Signature information signature: Mapped[Optional[str]] = mapped_column(String(1000)) + photo_path: Mapped[Optional[str]] = mapped_column(String(1000)) def __repr__(self): 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/dto.py b/backend/api/v1/modules/a76/general_catalogs/unit_conversions/dto.py index 85fd2ad9..0bb41aed 100644 --- a/backend/api/v1/modules/a76/general_catalogs/unit_conversions/dto.py +++ b/backend/api/v1/modules/a76/general_catalogs/unit_conversions/dto.py @@ -2,27 +2,23 @@ from typing import Optional from decimal import Decimal from pydantic import BaseModel, Field, ConfigDict - class UnitConversionBase(BaseModel): - from_unit_code: str = Field(..., max_length=5, - description="Source Unit Code") - to_unit_code: str = Field(..., max_length=5, - description="Target Unit Code") - conversion_factor: Optional[Decimal] = Field( - None, description="Conversion Factor") - + # 👇 OJO: Son códigos (Strings), no IDs + from_unit_code: str = Field(..., max_length=5, description="Source Unit Code") + to_unit_code: str = Field(..., max_length=5, description="Target Unit Code") + conversion_factor: Optional[Decimal] = Field(None, description="Conversion Factor") class UnitConversionCreate(UnitConversionBase): pass - class UnitConversionUpdate(BaseModel): from_unit_code: Optional[str] = Field(None, max_length=5) to_unit_code: Optional[str] = Field(None, max_length=5) conversion_factor: Optional[Decimal] = None - class UnitConversionResponse(UnitConversionBase): id: int + tenant_id: int + company_id: int - model_config = ConfigDict(from_attributes=True) + model_config = ConfigDict(from_attributes=True) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/general_catalogs/unit_conversions/models.py b/backend/api/v1/modules/a76/general_catalogs/unit_conversions/models.py index 65122a4c..26893749 100644 --- a/backend/api/v1/modules/a76/general_catalogs/unit_conversions/models.py +++ b/backend/api/v1/modules/a76/general_catalogs/unit_conversions/models.py @@ -27,8 +27,11 @@ class UnitConversion(Base, TenantScopedMixin, TimestampMixin): id: Mapped[int] = mapped_column( Integer, primary_key=True, autoincrement=True) + from_unit_code: Mapped[str] = mapped_column(String(5), nullable=False) + to_unit_code: Mapped[str] = mapped_column(String(5), nullable=False) + conversion_factor: Mapped[Optional[Decimal]] = mapped_column( Numeric(13, 6), nullable=True) 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/api/v1/modules/a76/general_catalogs/units_of_measure/models.py b/backend/api/v1/modules/a76/general_catalogs/units_of_measure/models.py index 04cae220..ffd359db 100644 --- a/backend/api/v1/modules/a76/general_catalogs/units_of_measure/models.py +++ b/backend/api/v1/modules/a76/general_catalogs/units_of_measure/models.py @@ -181,4 +181,4 @@ class UnitOfMeasureGeneral(Base, TenantScopedMixin, TimestampMixin): String(4), nullable=True) # CLAVEACE customs_unit: Mapped[Optional["UnitOfMeasureCustoms"]] = relationship() - ace_unit: Mapped[Optional["UnitOfMeasureACE"]] = relationship() + ace_unit: Mapped[Optional["UnitOfMeasureACE"]] = relationship(overlaps="customs_unit") diff --git a/backend/api/v1/modules/a76/general_catalogs/units_of_measure/service.py b/backend/api/v1/modules/a76/general_catalogs/units_of_measure/service.py index b21faa7a..19304472 100644 --- a/backend/api/v1/modules/a76/general_catalogs/units_of_measure/service.py +++ b/backend/api/v1/modules/a76/general_catalogs/units_of_measure/service.py @@ -1,6 +1,11 @@ from typing import List, Optional, Tuple, Dict, Any, Type from sqlalchemy import Sequence from sqlalchemy.orm import Session +from sqlalchemy.exc import IntegrityError +from fastapi import HTTPException +import logging + +logger = logging.getLogger(__name__) from .models import ( UnitOfMeasureACE, UnitOfMeasureOMA, UnitOfMeasureAmerican, UnitOfMeasureCustoms, @@ -102,9 +107,17 @@ class BaseService: if not db_obj: return False - db.delete(db_obj) - db.commit() - return True + try: + db.delete(db_obj) + db.commit() + return True + except IntegrityError as e: + db.rollback() + logger.error(f"Error de integridad al eliminar {cls.model.__name__} {id}: {str(e)}") + raise HTTPException( + status_code=400, + detail="No se puede eliminar esta unidad de medida porque tiene registros relacionados. Primero debe eliminar o reasignar esos registros." + ) class UnitOfMeasureACEService(BaseService): diff --git a/backend/api/v1/modules/a76/invoices/models.py b/backend/api/v1/modules/a76/invoices/models.py new file mode 100644 index 00000000..7f787def --- /dev/null +++ b/backend/api/v1/modules/a76/invoices/models.py @@ -0,0 +1,259 @@ +from enum import Enum +from typing import Optional, List +from sqlalchemy import BigInteger, Boolean, Date, ForeignKey, Integer, Numeric, String, Text, TIMESTAMP +from sqlalchemy.orm import Mapped, mapped_column, relationship +from core.database import Base +from datetime import datetime +from ....common.base_models import TenantScopedMixin, TimestampMixin + + +class OperationType(str, Enum): + IMP = "imp" # Importación + EXP = "exp" # Exportación + + +class TransportType(str, Enum): + NONE = "none" # Ninguno + TRANSPORT = "transport" # Transporte + BOX = "box" # Caja + PLATES = "licence plates" # Placas + TRUCK = "truck" # Camión + VESSEL = "vessel" # Buque + BARGE = "rail barge" # Ferrobarcaza + CONTAINER = "container" # Contenedor + AIRPLANE = "airplane" # Avión + GONDOLA = "gondola" # Góndola + FLATBED = "flatbed" # Plataforma + +# --- 1. Invoice Header (invoice_header) --- +class InvoiceHeader(Base, TenantScopedMixin, TimestampMixin): + __tablename__ = "invoice_header" + __table_args__ = ( + {"schema": "a76"}, + ) + + id: Mapped[int] = mapped_column( + BigInteger, primary_key=True, autoincrement=True) + + # Identifiers + operation_type: Mapped[OperationType] = mapped_column( + String(3)) # TIPOMOVIMIENTO / Clasifica imp/exp + invoice_type: Mapped[Optional[str]] = mapped_column(ForeignKey( + "public.invoice_types.key")) # TIPOFACTURA (invoice_types) + invoice_number: Mapped[Optional[str]] = mapped_column( + String(20)) # FACTURAIMPO/FACTURAEXPO/FACTURAREMISION + project_number: Mapped[Optional[str]] = mapped_column( + String(14)) # NUMPROYECTO + purchase_order: Mapped[Optional[str]] = mapped_column( + String(50)) # ORDENCOMPRA + related_doc_id: Mapped[Optional[int]] = mapped_column( + Integer) # IDRELDOC (Para Rectificaciones) + + # Dates + invoice_date: Mapped[Optional[datetime] + ] = mapped_column(Date) # FECHAFACTURA + capture_date: Mapped[datetime] = mapped_column( + TIMESTAMP(timezone=False), default=datetime.now) # FECHACAPTURA + HORAACTUAL + + # Status & Control + is_updated: Mapped[Optional[str]] = mapped_column(Boolean) # ESTATUS + updated_date: Mapped[Optional[datetime]] = mapped_column(TIMESTAMP(timezone=False)) # FECHAACTUALIZACION + who_updated: Mapped[Optional[str]] = mapped_column(String(20)) # QUIENA ACTUALIZO + + traffic_light_status: Mapped[Optional[str]] = mapped_column(String(50)) # SEMAFORO / SEMAFOROEXPO/IMPO + process_log: Mapped[Optional[str]] = mapped_column(String(300)) # COMOFUEPROCESADA + + # Comments + observation_es: Mapped[Optional[str]] = mapped_column(Text) #OBSERVACIONE + observation_en: Mapped[Optional[str]] = mapped_column(Text) #OBSERVACIONI + comments_status: Mapped[Optional[str]] = mapped_column(Text) #COMENTARIOSESTATUS + + # Digital Archive Links + cfdi_uuid: Mapped[Optional[str]] = mapped_column(String(100)) # CFDIUUID + path_pdf: Mapped[Optional[str]] = mapped_column(String(500)) # CFDIPATHPDF + path_xml: Mapped[Optional[str]] = mapped_column(String(500)) # CFDIPATHXML + + # Relationships (Para navegacion ORM) + compliance_mx: Mapped["InvoiceComplianceMx"] = relationship( + back_populates="header", cascade="all, delete-orphan") + financials: Mapped["InvoiceFinancials"] = relationship( + back_populates="header", cascade="all, delete-orphan") + details: Mapped[List["InvoiceSalesDetails"]] = relationship( + back_populates="header", cascade="all, delete-orphan") + collections: Mapped[List["InvoiceCollections"]] = relationship( + back_populates="header", cascade="all, delete-orphan") + logistics: Mapped[List["InvoiceLogistics"]] = relationship( + back_populates="header", cascade="all, delete-orphan") + + +# --- 2. Compliance MX (invoice_compliance_mx) --- +class InvoiceComplianceMx(Base, TenantScopedMixin, TimestampMixin): + __tablename__ = "invoice_compliance_mx" + __table_args__ = ( + {"schema": "a76"}, + ) + + invoice_id: Mapped[int] = mapped_column( + ForeignKey("a76.invoice_header.id"), primary_key=True) + + # Core Customs Data + pedimento: Mapped[Optional[str]] = mapped_column( + String(19)) # PEDIMENTO/PEDIMENTOIMPO/EXPO + pedimento_code: Mapped[Optional[str]] = mapped_column( + String(5)) # PEDIMENTOR1, K1 + remesa: Mapped[Optional[int]] = mapped_column(Integer) + aduana: Mapped[Optional[str]] = mapped_column(ForeignKey("public.customs_sections.customs_code")) # ADUANA_CRUCE + + # Clients & Providers + provider_header: Mapped[Optional[str]] = mapped_column(String(20)) # PROVEEDOREXPORTADOR + provider_id: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.clients_and_providers.id")) # PROVEEDOR + sold_to_header: Mapped[Optional[str]] = mapped_column(String(20)) # VENDIDOCONSIGNADO + sold_to_id: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.clients_and_providers.id")) # VENDIDOA + shipped_to_header: Mapped[Optional[str]] = mapped_column(String(20)) # ENVIADOTRANSFERIDO + shipped_to_id: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.clients_and_providers.id")) # ENVIADOA + shipped_by_header: Mapped[Optional[str]] = mapped_column(String(20)) # ENVIADOPORVENDIDOPOR + shipped_by_id: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.clients_and_providers.id")) # VENDIDOPOR + + customs_broker_id: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.customs_brokers.id")) # AADUANAL + + # Flags & Specific Regimes + is_mixed: Mapped[Optional[bool]] = mapped_column(Boolean) # ESMIXTO + waste_type: Mapped[Optional[str]] = mapped_column(String(1)) # TIPODESPERDICIO + appendix_17: Mapped[Optional[int]] = mapped_column(Integer) # APENDICE17 + + # VUCEM / Digital + edocument: Mapped[Optional[str]] = mapped_column(String(50)) # EDOCUMENT + electronic_signature: Mapped[Optional[str]] = mapped_column(String(999)) # FIRMAELECTRONICA + sem_id: Mapped[Optional[int]] = mapped_column(Integer) # SEM (de SFacEntradaSM/SFacSalidaSM) + + # Relationship + header: Mapped["InvoiceHeader"] = relationship( + back_populates="compliance_mx") + + +# --- 3. Financials (invoice_financials) --- +class InvoiceFinancials(Base, TenantScopedMixin, TimestampMixin): + __tablename__ = "invoice_financials" + __table_args__ = ( + {"schema": "a76"}, + ) + + id: Mapped[int] = mapped_column( + BigInteger, primary_key=True, autoincrement=True) + invoice_id: Mapped[int] = mapped_column(ForeignKey("a76.invoice_header.id")) + + currency: Mapped[Optional[str]] = mapped_column(String(3)) # CLAVEMONEDA + currency_type: Mapped[Optional[str]] = mapped_column(ForeignKey("public.currency_types.code")) # TIPOCLAVEMONEDA + exchange_rate: Mapped[Optional[float]] = mapped_column(Numeric(13, 6)) # TIPOCAMBIO + + # Merchandise Values + value_mn: Mapped[Optional[float]] = mapped_column(Numeric(23, 8), default=0) # VALORIMPOMN/EXPOMN + value_me: Mapped[Optional[float]] = mapped_column(Numeric(23, 8), default=0) + customs_value_mn: Mapped[Optional[float]] = mapped_column( Numeric(23, 8), default=0) # VALORADUANASMN + + # Costs & Taxes + freight: Mapped[Optional[float]] = mapped_column(Numeric(19, 8), default=0) # FLETE + insurance: Mapped[Optional[float]] = mapped_column(Numeric(19, 8), default=0) # VALSEGUROS/SEGUROS + iva_mn: Mapped[Optional[float]] = mapped_column(Numeric(23, 8), default=0) # IVAEXPOMN / VALORIVAMN + iva_factor: Mapped[Optional[float]] = mapped_column(Numeric(17, 4)) # FACTORIVA + + # Weights & Quantities + total_quantity: Mapped[Optional[float]] = mapped_column(Numeric(19, 8)) # CANTEXPO / CANTIMPO + gross_weight: Mapped[Optional[float]] = mapped_column(Numeric(19, 8)) # PESOBRUTO + net_weight: Mapped[Optional[float]] = mapped_column(Numeric(19, 8)) # PESONETO + bundle_count: Mapped[Optional[int]] = mapped_column(Integer) # CANTBULTOS + + # Relationship + header: Mapped["InvoiceHeader"] = relationship(back_populates="financials") + + +# --- 4. Logistics (invoice_logistics) --- +class InvoiceLogistics(Base, TenantScopedMixin, TimestampMixin): + __tablename__ = "invoice_logistics" + __table_args__ = ( + {"schema": "a76"}, + ) + + id: Mapped[int] = mapped_column( + BigInteger, primary_key=True, autoincrement=True) + invoice_id: Mapped[int] = mapped_column(ForeignKey("a76.invoice_header.id")) + + # Carrier Info + carrier_id: Mapped[Optional[str]] = mapped_column( + String(10)) # TRANSPORTISTA + transport_type: Mapped[TransportType] = mapped_column( + String(15), default="none") # TRANSPORTE + transport_mode: Mapped[Optional[str]] = mapped_column( + String(15)) # MODTRANS + driver_name: Mapped[Optional[str]] = mapped_column(String(80)) # CONDUCTOR + is_rail: Mapped[Optional[str]] = mapped_column(String(2)) # ESFERROCARRIL + rail_id: Mapped[Optional[str]] = mapped_column( + String(31)) # IDFERRORCARRIL + + # Vehicle & Tracking + vehicle_num: Mapped[Optional[str]] = mapped_column( + String(20)) # NUMVEHICULO / NUMTRAILER + license_plate: Mapped[Optional[str]] = mapped_column( + String(20)) # NUMTRASPORTE + seal_number: Mapped[Optional[str]] = mapped_column(String(15)) # PRECINTO + guide_number: Mapped[Optional[str]] = mapped_column( + String(20)) # NUMEROGUIA + + # Logistics Dates + entry_exit_date: Mapped[Optional[datetime]] = mapped_column( + Date) # FECHAENTRADA / FECHAENVIO + + # Relationship + header: Mapped["InvoiceHeader"] = relationship(back_populates="logistics") + + +# --- 5. Sales Order Details (invoice_sales_details) --- +class InvoiceSalesDetails(Base, TenantScopedMixin, TimestampMixin): + __tablename__ = "invoice_sales_details" + __table_args__ = ( + {"schema": "a76"}, + ) + + id: Mapped[int] = mapped_column( + BigInteger, primary_key=True, autoincrement=True) + invoice_id: Mapped[int] = mapped_column(ForeignKey("a76.invoice_header.id")) + + line_number: Mapped[int] = mapped_column(Integer) # LINEA + sales_order: Mapped[Optional[str]] = mapped_column( + String(20)) # ORDENVENTA + + # Specific Custom Fields + colors_description: Mapped[Optional[str] + ] = mapped_column(String(49)) # COLORES + square_color_code: Mapped[Optional[str]] = mapped_column( + String(1)) # COLORCUADRITO + line_bundles: Mapped[Optional[int]] = mapped_column( + Integer) # CANTBULTOS (de la linea) + + # Relationship + header: Mapped["InvoiceHeader"] = relationship(back_populates="details") + + +# --- 6. Collections (invoice_collections) --- +class InvoiceCollections(Base, TenantScopedMixin, TimestampMixin): + __tablename__ = "invoice_collections" + __table_args__ = ( + {"schema": "a76"}, + ) + + id: Mapped[int] = mapped_column( + BigInteger, primary_key=True, autoincrement=True) + invoice_id: Mapped[int] = mapped_column(ForeignKey("a76.invoice_header.id")) + + concept: Mapped[Optional[str]] = mapped_column(String(100)) # CONCEPTO + is_collected: Mapped[Optional[int]] = mapped_column(Integer) # COBRADO + collection_date: Mapped[Optional[datetime] + ] = mapped_column(Date) # FECHACOBRANZA + amount: Mapped[Optional[float]] = mapped_column(Numeric(23, 8)) # VALOR + + collector_user: Mapped[Optional[str]] = mapped_column( + String(20)) # FACTCOBRADOR + + # Relationship + header: Mapped["InvoiceHeader"] = relationship( + back_populates="collections") diff --git a/backend/api/v1/modules/a76/invoices/routes.py b/backend/api/v1/modules/a76/invoices/routes.py new file mode 100644 index 00000000..e81706a1 --- /dev/null +++ b/backend/api/v1/modules/a76/invoices/routes.py @@ -0,0 +1,284 @@ +from typing import Dict, Any +from api.v1.common.tenant_crud_routes import TenantCRUDRoutes +from core.database import get_core_db +from core.security import get_current_user, validate_access_to_resource +from fastapi import APIRouter, Depends, HTTPException, Query, Path +from sqlalchemy.orm import Session + +from . import schemas, services + +# Create main router +router = APIRouter() + +# Create CRUD routes for Invoice Header using TenantCRUDRoutes +invoice_crud = TenantCRUDRoutes( + service=services.InvoiceService, + create_schema=schemas.InvoiceHeaderCreate, + update_schema=schemas.InvoiceHeaderUpdate, + response_schema=schemas.InvoiceHeaderResponse, + prefix="/invoices", + tags=[], + resource_name="Invoice", + id_name="invoice_id", + id_type=int, + enable_list=True, # Enable list endpoint with pagination + enable_filters=True, # Enable filters for status, operation_type, etc. + default_page_size=50, + max_page_size=200, +) + +# Include the main CRUD routes +router.include_router(invoice_crud.router) + + +# Additional nested routes for child resources + +# --- Logistics Routes --- + +@router.get( + "/invoices/{invoice_id}/logistics", + response_model=list[schemas.InvoiceLogisticsResponse], + summary="Get all logistics for an invoice", +) +def get_invoice_logistics( + invoice_id: int = Path(..., description="Invoice ID"), + company_id: int = Query(..., description="Company ID"), + db: Session = Depends(get_core_db), + current_user: Dict[str, Any] = Depends(get_current_user), +): + """Get all logistics entries for a specific invoice""" + tenant_id = validate_access_to_resource(db, company_id, current_user) + + # Verify the invoice exists and belongs to the tenant/company + invoice = services.InvoiceService.get_by_id( + db, invoice_id, tenant_id, company_id) + if not invoice: + raise HTTPException(status_code=404, detail="Invoice not found") + + logistics = services.InvoiceLogisticsService.get_all_by_invoice( + db, invoice_id) + return logistics + + +@router.post( + "/invoices/{invoice_id}/logistics", + response_model=schemas.InvoiceLogisticsResponse, + status_code=201, + summary="Add logistics to an invoice", +) +def create_invoice_logistics( + invoice_id: int = Path(..., description="Invoice ID"), + logistics_data: schemas.InvoiceLogisticsCreate = ..., + company_id: int = Query(..., description="Company ID"), + db: Session = Depends(get_core_db), + current_user: Dict[str, Any] = Depends(get_current_user), +): + """Add a new logistics entry to an invoice""" + tenant_id = validate_access_to_resource(db, company_id, current_user) + + # Verify the invoice exists and belongs to the tenant/company + invoice = services.InvoiceService.get_by_id( + db, invoice_id, tenant_id, company_id) + if not invoice: + raise HTTPException(status_code=404, detail="Invoice not found") + + logistics = services.InvoiceLogisticsService.create( + db, logistics_data, invoice_id, tenant_id, company_id) + return logistics + + +@router.delete( + "/invoices/{invoice_id}/logistics/{logistics_id}", + status_code=204, + summary="Delete logistics from an invoice", +) +def delete_invoice_logistics( + invoice_id: int = Path(..., description="Invoice ID"), + logistics_id: int = Path(..., description="Logistics ID"), + company_id: int = Query(..., description="Company ID"), + db: Session = Depends(get_core_db), + current_user: Dict[str, Any] = Depends(get_current_user), +): + """Delete a logistics entry from an invoice""" + tenant_id = validate_access_to_resource(db, company_id, current_user) + + # Verify the invoice exists and belongs to the tenant/company + invoice = services.InvoiceService.get_by_id( + db, invoice_id, tenant_id, company_id) + if not invoice: + raise HTTPException(status_code=404, detail="Invoice not found") + + success = services.InvoiceLogisticsService.delete( + db, logistics_id, invoice_id) + if not success: + raise HTTPException( + status_code=404, detail="Logistics entry not found") + + return None + + +# --- Sales Details Routes --- + +@router.get( + "/invoices/{invoice_id}/details", + response_model=list[schemas.InvoiceSalesDetailsResponse], + summary="Get all sales details for an invoice", +) +def get_invoice_details( + invoice_id: int = Path(..., description="Invoice ID"), + company_id: int = Query(..., description="Company ID"), + db: Session = Depends(get_core_db), + current_user: Dict[str, Any] = Depends(get_current_user), +): + """Get all sales details for a specific invoice""" + tenant_id = validate_access_to_resource(db, company_id, current_user) + + # Verify the invoice exists and belongs to the tenant/company + invoice = services.InvoiceService.get_by_id( + db, invoice_id, tenant_id, company_id) + if not invoice: + raise HTTPException(status_code=404, detail="Invoice not found") + + details = services.InvoiceSalesDetailsService.get_all_by_invoice( + db, invoice_id) + return details + + +@router.post( + "/invoices/{invoice_id}/details", + response_model=schemas.InvoiceSalesDetailsResponse, + status_code=201, + summary="Add sales detail to an invoice", +) + +def create_invoice_detail( + invoice_id: int = Path(..., description="Invoice ID"), + detail_data: schemas.InvoiceSalesDetailsCreate = ..., + company_id: int = Query(..., description="Company ID"), + db: Session = Depends(get_core_db), + current_user: Dict[str, Any] = Depends(get_current_user), +): + """Add a new sales detail to an invoice""" + tenant_id = validate_access_to_resource(db, company_id, current_user) + + # Verify the invoice exists and belongs to the tenant/company + invoice = services.InvoiceService.get_by_id( + db, invoice_id, tenant_id, company_id) + if not invoice: + raise HTTPException(status_code=404, detail="Invoice not found") + + detail = services.InvoiceSalesDetailsService.create( + db, detail_data, invoice_id, tenant_id, company_id) + return detail + + +@router.delete( + "/invoices/{invoice_id}/details/{detail_id}", + status_code=204, + summary="Delete sales detail from an invoice", +) +def delete_invoice_detail( + invoice_id: int = Path(..., description="Invoice ID"), + detail_id: int = Path(..., description="Detail ID"), + company_id: int = Query(..., description="Company ID"), + db: Session = Depends(get_core_db), + current_user: Dict[str, Any] = Depends(get_current_user), +): + """Delete a sales detail from an invoice""" + tenant_id = validate_access_to_resource(db, company_id, current_user) + + # Verify the invoice exists and belongs to the tenant/company + invoice = services.InvoiceService.get_by_id( + db, invoice_id, tenant_id, company_id) + if not invoice: + raise HTTPException(status_code=404, detail="Invoice not found") + + success = services.InvoiceSalesDetailsService.delete( + db, detail_id, invoice_id) + if not success: + raise HTTPException(status_code=404, detail="Sales detail not found") + + return None + + +# --- Collections Routes --- + +@router.get( + "/invoices/{invoice_id}/collections", + response_model=list[schemas.InvoiceCollectionsResponse], + summary="Get all collections for an invoice", +) +def get_invoice_collections( + invoice_id: int = Path(..., description="Invoice ID"), + company_id: int = Query(..., description="Company ID"), + db: Session = Depends(get_core_db), + current_user: Dict[str, Any] = Depends(get_current_user), +): + """Get all collections for a specific invoice""" + tenant_id = validate_access_to_resource(db, company_id, current_user) + + # Verify the invoice exists and belongs to the tenant/company + invoice = services.InvoiceService.get_by_id( + db, invoice_id, tenant_id, company_id) + if not invoice: + raise HTTPException(status_code=404, detail="Invoice not found") + + collections = services.InvoiceCollectionsService.get_all_by_invoice( + db, invoice_id) + return collections + + +@router.post( + "/invoices/{invoice_id}/collections", + response_model=schemas.InvoiceCollectionsResponse, + status_code=201, + summary="Add collection to an invoice", +) +def create_invoice_collection( + invoice_id: int = Path(..., description="Invoice ID"), + collection_data: schemas.InvoiceCollectionsCreate = ..., + company_id: int = Query(..., description="Company ID"), + db: Session = Depends(get_core_db), + current_user: Dict[str, Any] = Depends(get_current_user), +): + """Add a new collection to an invoice""" + tenant_id = validate_access_to_resource(db, company_id, current_user) + + # Verify the invoice exists and belongs to the tenant/company + invoice = services.InvoiceService.get_by_id( + db, invoice_id, tenant_id, company_id) + if not invoice: + raise HTTPException(status_code=404, detail="Invoice not found") + + collection = services.InvoiceCollectionsService.create( + db, collection_data, invoice_id, tenant_id, company_id) + return collection + + +@router.delete( + "/invoices/{invoice_id}/collections/{collection_id}", + status_code=204, + summary="Delete collection from an invoice", +) +def delete_invoice_collection( + invoice_id: int = Path(..., description="Invoice ID"), + collection_id: int = Path(..., description="Collection ID"), + company_id: int = Query(..., description="Company ID"), + db: Session = Depends(get_core_db), + current_user: Dict[str, Any] = Depends(get_current_user), +): + """Delete a collection from an invoice""" + tenant_id = validate_access_to_resource(db, company_id, current_user) + + # Verify the invoice exists and belongs to the tenant/company + invoice = services.InvoiceService.get_by_id( + db, invoice_id, tenant_id, company_id) + if not invoice: + raise HTTPException(status_code=404, detail="Invoice not found") + + success = services.InvoiceCollectionsService.delete( + db, collection_id, invoice_id) + if not success: + raise HTTPException(status_code=404, detail="Collection not found") + + return None diff --git a/backend/api/v1/modules/a76/invoices/schemas.py b/backend/api/v1/modules/a76/invoices/schemas.py new file mode 100644 index 00000000..8075cd94 --- /dev/null +++ b/backend/api/v1/modules/a76/invoices/schemas.py @@ -0,0 +1,257 @@ +from typing import Optional, List +from datetime import datetime, date +from decimal import Decimal +from pydantic import BaseModel, Field +from .models import OperationType + + +# --- Base Schemas --- +class InvoiceHeaderBase(BaseModel): + """Base fields for Invoice Header""" + operation_type: Optional[OperationType] = Field( + None, max_length=20, description="Operation type: imp/exp") + invoice_type: Optional[str] = Field( + None, max_length=5, description="Invoice type key") + invoice_number: Optional[str] = Field( + None, max_length=20, description="Invoice number") + project_number: Optional[str] = Field( + None, max_length=14, description="Project number") + purchase_order: Optional[str] = Field( + None, max_length=50, description="Purchase order") + related_doc_id: Optional[int] = Field( + None, description="Related document ID for rectifications") + invoice_date: Optional[date] = Field(None, description="Invoice date") + is_updated: Optional[bool] = Field(None, description="Status") + traffic_light_status: Optional[str] = Field( + None, max_length=50, description="Traffic light status (SEMAFORO)") + process_log: Optional[str] = Field( + None, max_length=300, description="Processing log") + comments_status: Optional[str] = Field( + None, description="Comments and observations") + cfdi_uuid: Optional[str] = Field( + None, max_length=100, description="CFDI UUID") + path_pdf: Optional[str] = Field( + None, max_length=500, description="Path to PDF file") + path_xml: Optional[str] = Field( + None, max_length=500, description="Path to XML file") + + +class InvoiceComplianceMxBase(BaseModel): + """Base fields for Compliance MX""" + pedimento: Optional[str] = Field( + None, max_length=19, description="Pedimento number") + pedimento_code: Optional[str] = Field( + None, max_length=5, description="Pedimento code (R1/K1)") + remesa: Optional[int] = Field(None, description="Remesa") + aduana: Optional[str] = Field( + None, max_length=5, description="Customs office") + customs_agent: Optional[str] = Field( + None, max_length=10, description="Customs agent") + is_mixed: Optional[bool] = Field( + None, description="Is mixed operation") + waste_type: Optional[str] = Field( + None, max_length=1, description="Waste type") + appendix_17: Optional[int] = Field(None, description="Appendix 17") + edocument: Optional[str] = Field( + None, max_length=50, description="E-document") + electronic_signature: Optional[str] = Field( + None, max_length=999, description="Electronic signature") + sem_id: Optional[int] = Field(None, description="SEM ID") + + +class InvoiceFinancialsBase(BaseModel): + """Base fields for Financials""" + currency: Optional[str] = Field( + None, max_length=3, description="Currency code") + exchange_rate: Optional[Decimal] = Field(None, description="Exchange rate") + value_mn: Optional[Decimal] = Field(None, description="Value in MXN") + value_me: Optional[Decimal] = Field( + None, description="Value in foreign currency") + customs_value_mn: Optional[Decimal] = Field( + None, description="Customs value in MXN") + freight: Optional[Decimal] = Field(None, description="Freight cost") + insurance: Optional[Decimal] = Field(None, description="Insurance cost") + iva_mn: Optional[Decimal] = Field(None, description="IVA in MXN") + iva_factor: Optional[Decimal] = Field(None, description="IVA factor") + total_quantity: Optional[Decimal] = Field( + None, description="Total quantity") + gross_weight: Optional[Decimal] = Field(None, description="Gross weight") + net_weight: Optional[Decimal] = Field(None, description="Net weight") + bundle_count: Optional[int] = Field(None, description="Bundle count") + + +class InvoiceLogisticsBase(BaseModel): + """Base fields for Logistics""" + carrier_id: Optional[str] = Field( + None, max_length=10, description="Carrier ID") + transport_mode: Optional[str] = Field( + None, max_length=15, description="Transport mode") + driver_name: Optional[str] = Field( + None, max_length=80, description="Driver name") + is_rail: Optional[str] = Field( + None, max_length=2, description="Is rail transport") + rail_id: Optional[str] = Field(None, max_length=31, description="Rail ID") + vehicle_num: Optional[str] = Field( + None, max_length=20, description="Vehicle number") + license_plate: Optional[str] = Field( + None, max_length=20, description="License plate") + seal_number: Optional[str] = Field( + None, max_length=15, description="Seal number") + guide_number: Optional[str] = Field( + None, max_length=20, description="Guide number") + entry_exit_date: Optional[date] = Field( + None, description="Entry/Exit date") + + +class InvoiceSalesDetailsBase(BaseModel): + """Base fields for Sales Details""" + line_number: int = Field(..., description="Line number") + sales_order: Optional[str] = Field( + None, max_length=20, description="Sales order") + colors_description: Optional[str] = Field( + None, max_length=49, description="Colors description") + square_color_code: Optional[str] = Field( + None, max_length=1, description="Square color code") + line_bundles: Optional[int] = Field(None, description="Line bundles count") + + +class InvoiceCollectionsBase(BaseModel): + """Base fields for Collections""" + concept: Optional[str] = Field(None, max_length=100, description="Concept") + is_collected: Optional[int] = Field(None, description="Is collected flag") + collection_date: Optional[date] = Field( + None, description="Collection date") + amount: Optional[Decimal] = Field(None, description="Amount") + collector_user: Optional[str] = Field( + None, max_length=20, description="Collector user") + + +# --- Create Schemas --- + +class InvoiceComplianceMxCreate(InvoiceComplianceMxBase): + """Schema for creating Compliance MX""" + pass + + +class InvoiceFinancialsCreate(InvoiceFinancialsBase): + """Schema for creating Financials""" + pass + + +class InvoiceLogisticsCreate(InvoiceLogisticsBase): + """Schema for creating Logistics""" + pass + + +class InvoiceSalesDetailsCreate(InvoiceSalesDetailsBase): + """Schema for creating Sales Details""" + pass + + +class InvoiceCollectionsCreate(InvoiceCollectionsBase): + """Schema for creating Collections""" + pass + + +class InvoiceHeaderCreate(InvoiceHeaderBase): + """Schema for creating Invoice Header with nested relations""" + compliance_mx: Optional[InvoiceComplianceMxCreate] = None + financials: Optional[InvoiceFinancialsCreate] = None + logistics: Optional[List[InvoiceLogisticsCreate]] = None + details: Optional[List[InvoiceSalesDetailsCreate]] = None + collections: Optional[List[InvoiceCollectionsCreate]] = None + + +# --- Update Schemas --- + +class InvoiceComplianceMxUpdate(InvoiceComplianceMxBase): + """Schema for updating Compliance MX""" + pass + + +class InvoiceFinancialsUpdate(InvoiceFinancialsBase): + """Schema for updating Financials""" + pass + + +class InvoiceLogisticsUpdate(InvoiceLogisticsBase): + """Schema for updating Logistics""" + pass + + +class InvoiceSalesDetailsUpdate(InvoiceSalesDetailsBase): + """Schema for updating Sales Details""" + line_number: Optional[int] = None + + +class InvoiceCollectionsUpdate(InvoiceCollectionsBase): + """Schema for updating Collections""" + pass + + +class InvoiceHeaderUpdate(InvoiceHeaderBase): + """Schema for updating Invoice Header with nested relations""" + compliance_mx: Optional[InvoiceComplianceMxUpdate] = None + financials: Optional[InvoiceFinancialsUpdate] = None + logistics: Optional[List[InvoiceLogisticsUpdate]] = None + details: Optional[List[InvoiceSalesDetailsUpdate]] = None + collections: Optional[List[InvoiceCollectionsUpdate]] = None + + +# --- Response Schemas --- + +class InvoiceComplianceMxResponse(InvoiceComplianceMxBase): + """Schema for Compliance MX response""" + invoice_id: int + + class Config: + from_attributes = True + + +class InvoiceFinancialsResponse(InvoiceFinancialsBase): + """Schema for Financials response""" + invoice_id: int + + class Config: + from_attributes = True + + +class InvoiceLogisticsResponse(InvoiceLogisticsBase): + """Schema for Logistics response""" + logistics_id: int + invoice_id: int + + class Config: + from_attributes = True + + +class InvoiceSalesDetailsResponse(InvoiceSalesDetailsBase): + """Schema for Sales Details response""" + detail_id: int + invoice_id: int + + class Config: + from_attributes = True + + +class InvoiceCollectionsResponse(InvoiceCollectionsBase): + """Schema for Collections response""" + collection_id: int + invoice_id: int + + class Config: + from_attributes = True + + +class InvoiceHeaderResponse(InvoiceHeaderBase): + """Schema for Invoice Header response with nested relations""" + id: int + capture_date: datetime + compliance_mx: Optional[InvoiceComplianceMxResponse] = None + financials: Optional[InvoiceFinancialsResponse] = None + logistics: List[InvoiceLogisticsResponse] = [] + details: List[InvoiceSalesDetailsResponse] = [] + collections: List[InvoiceCollectionsResponse] = [] + + class Config: + from_attributes = True diff --git a/backend/api/v1/modules/a76/invoices/services.py b/backend/api/v1/modules/a76/invoices/services.py new file mode 100644 index 00000000..478bdae8 --- /dev/null +++ b/backend/api/v1/modules/a76/invoices/services.py @@ -0,0 +1,256 @@ +import traceback +from typing import Optional, List, Tuple +from sqlalchemy.orm import Session +from sqlalchemy import and_ + +from . import models, schemas + +class InvoiceService: + """Service for Invoice Header operations""" + + @staticmethod + def get_by_id(db: Session, invoice_id: int, tenant_id: int, company_id: int) -> Optional[models.InvoiceHeader]: + """Get an invoice by ID with tenant/company validation""" + return ( + db.query(models.InvoiceHeader) + .filter( + models.InvoiceHeader.id == invoice_id, + models.InvoiceHeader.tenant_id == tenant_id, + models.InvoiceHeader.company_id == company_id, + ) + .first() + ) + + @staticmethod + def get_all( + db: Session, + tenant_id: int, + company_id: int, + skip: int = 0, + limit: int = 100, + filters: Optional[dict] = None, + ) -> Tuple[List[models.InvoiceHeader], int]: + """Get all invoices for a tenant/company with pagination and optional filters""" + query = db.query(models.InvoiceHeader).filter( + models.InvoiceHeader.tenant_id == tenant_id, + models.InvoiceHeader.company_id == company_id, + ) + + # Apply filters if provided + if filters: + if filters.get("status"): + query = query.filter( + models.InvoiceHeader.status == filters["status"]) + if filters.get("operation_type"): + query = query.filter( + models.InvoiceHeader.operation_type == filters["operation_type"]) + if filters.get("invoice_type"): + query = query.filter( + models.InvoiceHeader.invoice_type == filters["invoice_type"]) + if filters.get("invoice_number"): + query = query.filter(models.InvoiceHeader.invoice_number.ilike( + f"%{filters['invoice_number']}%")) + if filters.get("pedimento"): + query = query.join(models.InvoiceComplianceMx).filter( + models.InvoiceComplianceMx.pedimento.ilike( + f"%{filters['pedimento']}%") + ) + if not filters.get("invoice_type") and filters.get("operation_type") == "exp": + query = query.filter( + models.InvoiceHeader.operation_type != "REPAR") + + total = query.count() + items = query.offset(skip).limit(limit).all() + return items, total + + @staticmethod + def create( + db: Session, + invoice_data: schemas.InvoiceHeaderCreate, + tenant_id: int, + company_id: int + ) -> models.InvoiceHeader: + """Create a new invoice with all related data""" + + + def clean_dict(data_dict: dict) -> dict: + cleaned = {} + for key, value in data_dict.items(): + + if key == 'customs_agent': + key = 'customs_broker_id' + elif key == 'provider': + key = 'provider_id' + + + if isinstance(value, str) and not value.strip(): + cleaned[key] = None + + elif value == 0 and (key.endswith('_id') or key == 'remesa'): + cleaned[key] = None + else: + cleaned[key] = value + return cleaned + + + try: + # Extract nested data + compliance_data = invoice_data.compliance_mx + financials_data = invoice_data.financials + logistics_data = invoice_data.logistics or [] + details_data = invoice_data.details or [] + collections_data = invoice_data.collections or [] + + # Create main invoice header + raw_invoice_dict = invoice_data.model_dump( + exclude={"compliance_mx", "financials", + "logistics", "details", "collections"} + ) + invoice_dict = clean_dict(raw_invoice_dict) + invoice_dict["tenant_id"] = tenant_id + invoice_dict["company_id"] = company_id + + new_invoice = models.InvoiceHeader(**invoice_dict) + db.add(new_invoice) + db.flush() # Flush to get the invoice ID + + # Create compliance_mx if provided + if compliance_data: + raw_comp_dict = compliance_data.model_dump() + # Pasamos los datos por la lavadora para arreglar pedimento, aduana, etc. + compliance_dict = clean_dict(raw_comp_dict) + + compliance_dict["invoice_id"] = new_invoice.id + compliance_dict["tenant_id"] = tenant_id + compliance_dict["company_id"] = company_id + + new_compliance = models.InvoiceComplianceMx(**compliance_dict) + db.add(new_compliance) + + # Create financials if provided + if financials_data: + raw_fin_dict = financials_data.model_dump() + financials_dict = clean_dict(raw_fin_dict) + + financials_dict["invoice_id"] = new_invoice.id + financials_dict["tenant_id"] = tenant_id + financials_dict["company_id"] = company_id + + new_financials = models.InvoiceFinancials(**financials_dict) + db.add(new_financials) + + # Create logistics entries + for logistics_item in logistics_data: + raw_log_dict = logistics_item.model_dump() + logistics_dict = clean_dict(raw_log_dict) + + logistics_dict["invoice_id"] = new_invoice.id + logistics_dict["tenant_id"] = tenant_id + logistics_dict["company_id"] = company_id + new_logistics = models.InvoiceLogistics(**logistics_dict) + db.add(new_logistics) + + # Create sales details + for detail_item in details_data: + raw_det_dict = detail_item.model_dump() + detail_dict = clean_dict(raw_det_dict) + + detail_dict["invoice_id"] = new_invoice.id + detail_dict["tenant_id"] = tenant_id + detail_dict["company_id"] = company_id + new_detail = models.InvoiceSalesDetails(**detail_dict) + db.add(new_detail) + + # Create collections + for collection_item in collections_data: + raw_col_dict = collection_item.model_dump() + collection_dict = clean_dict(raw_col_dict) + + collection_dict["invoice_id"] = new_invoice.id + collection_dict["tenant_id"] = tenant_id + collection_dict["company_id"] = company_id + new_collection = models.InvoiceCollections(**collection_dict) + db.add(new_collection) + + db.commit() + db.refresh(new_invoice) + return new_invoice + + except Exception as e: + db.rollback() + print("\n\n🔥 ERROR AL GUARDAR FACTURA 🔥") + print(f"Error: {str(e)}") + traceback.print_exc() # Esto imprime el error real en la consola + print("--------------------------------\n") + raise e + + @staticmethod + def update( + db: Session, + invoice_id: int, + tenant_id: int, + invoice_data: schemas.InvoiceHeaderUpdate, + company_id: int + ) -> Optional[models.InvoiceHeader]: + # ... (El resto de tu código update se queda igual) ... + # (Te recomiendo implementar clean_dict aquí también si tienes problemas al editar) + invoice = InvoiceService.get_by_id( + db, invoice_id, tenant_id, company_id) + if not invoice: + return None + + # Update main invoice header fields + update_dict = invoice_data.model_dump( + exclude={"compliance_mx", "financials", + "logistics", "details", "collections"}, + exclude_unset=True + ) + for key, value in update_dict.items(): + setattr(invoice, key, value) + + # Update compliance_mx if provided + if invoice_data.compliance_mx is not None: + if invoice.compliance_mx: + for key, value in invoice_data.compliance_mx.model_dump(exclude_unset=True).items(): + # Parche rápido para update + if value == "": value = None + setattr(invoice.compliance_mx, key, value) + else: + compliance_dict = invoice_data.compliance_mx.model_dump() + # Aplicar limpieza manual si es necesario + if 'customs_agent' in compliance_dict: compliance_dict['customs_broker_id'] = compliance_dict.pop('customs_agent') + + compliance_dict["invoice_id"] = invoice.id + compliance_dict["tenant_id"] = tenant_id + compliance_dict["company_id"] = company_id + new_compliance = models.InvoiceComplianceMx(**compliance_dict) + db.add(new_compliance) + + # Update financials if provided + if invoice_data.financials is not None: + if invoice.financials: + for key, value in invoice_data.financials.model_dump(exclude_unset=True).items(): + if value == "": value = None + setattr(invoice.financials, key, value) + else: + financials_dict = invoice_data.financials.model_dump() + financials_dict["invoice_id"] = invoice.id + financials_dict["tenant_id"] = tenant_id + financials_dict["company_id"] = company_id + new_financials = models.InvoiceFinancials(**financials_dict) + db.add(new_financials) + + db.commit() + db.refresh(invoice) + return invoice + + @staticmethod + def delete(db: Session, invoice_id: int, tenant_id: int, company_id: int) -> bool: + """Delete an invoice and all related data (cascade delete)""" + invoice = InvoiceService.get_by_id( + db, invoice_id, tenant_id, company_id) + if invoice: + db.delete(invoice) + db.commit() + return True + return False \ No newline at end of file diff --git a/backend/api/v1/modules/a76/router.py b/backend/api/v1/modules/a76/router.py index aed3aa5a..8bfee681 100644 --- a/backend/api/v1/modules/a76/router.py +++ b/backend/api/v1/modules/a76/router.py @@ -8,7 +8,7 @@ from fastapi import APIRouter from .customs_brokers.routes import router as customs_broker_router # Importar routers de módulos -from ..core.auth import router as auth_router +from .invoices.routes import router as invoices_router from .classes import router as classes_router from .clients_and_providers import router as client_and_provider_router from .general_catalogs.company import router as company_router @@ -17,7 +17,6 @@ from .transportation.drivers.routes import router as drivers_router from .general_catalogs.exchange_rate.routes import router as exchange_rate_router from .general_catalogs.identifiers.routes import router as identifiers_router from .fraction_rule_octave.routes import router as fraction_rule_octave_router -from ..core.licenses import router as licenses_router from .general_catalogs.packages.routes import router as package_router from .general_catalogs.ports.routes import router as ports_router from .parts import router as parts_router @@ -38,21 +37,15 @@ from .general_catalogs.error_catalogs.routes import router as error_catalogs_rou from .general_catalogs.doda.routes import router as doda_router from .general_catalogs.prevalidators.routes import router as prevalidators_router from .general_catalogs.electronic_notices.routes import router as electronic_notices_router -from ..core.tenants import router as tenants_router from .transportation.trailers.routes import router as trailers_router from .transportation.transporters.routes import router as transporters_router -from ..core.user_tenant.routes import router as user_tenant_router from .transportation.vehicles.routes import router as vehicles_router # Router principal router = APIRouter() # Registrar módulos -router.include_router(auth_router) -router.include_router(tenants_router, prefix="/a76", tags=["a76 / tenants"]) -router.include_router(user_tenant_router, prefix="/a76", - tags=["a76 / user-tenants"]) -router.include_router(licenses_router, prefix="/a76", tags=["a76 / licenses"]) +router.include_router(invoices_router, prefix="/a76", tags=["a76 / invoices"]) router.include_router(pedimentos_router, prefix="/a76") router.include_router( client_and_provider_router, prefix="/a76", tags=["a76 / clients_and_providers"] diff --git a/backend/api/v1/modules/core/router.py b/backend/api/v1/modules/core/router.py new file mode 100644 index 00000000..66b5b0d2 --- /dev/null +++ b/backend/api/v1/modules/core/router.py @@ -0,0 +1,12 @@ +from .auth.routes import router as auth_router +from .licenses.routes import router as licenses_router +from .tenants.routes import router as tenants_router +from .user_tenant.routes import router as user_tenant_router +from fastapi import APIRouter + +router = APIRouter() + +router.include_router(auth_router) +router.include_router(tenants_router, prefix="/core", tags=["core / tenants"]) +router.include_router(user_tenant_router, prefix="/core", tags=["core / user-tenants"]) +router.include_router(licenses_router, prefix="/core", tags=["core / licenses"]) \ No newline at end of file diff --git a/backend/api/v1/modules/public/reference_data/invoice_types/dto.py b/backend/api/v1/modules/public/reference_data/invoice_types/dto.py index 7e84a068..0488a2a2 100644 --- a/backend/api/v1/modules/public/reference_data/invoice_types/dto.py +++ b/backend/api/v1/modules/public/reference_data/invoice_types/dto.py @@ -8,5 +8,6 @@ class InvoiceTypeDTO(BaseModel): description: str note: Optional[str] = None type: Optional[str] = None + operation: Optional[str] = None model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/public/reference_data/invoice_types/models.py b/backend/api/v1/modules/public/reference_data/invoice_types/models.py index 8b3671f0..792cda91 100644 --- a/backend/api/v1/modules/public/reference_data/invoice_types/models.py +++ b/backend/api/v1/modules/public/reference_data/invoice_types/models.py @@ -10,15 +10,11 @@ class InvoiceType(Base): {"schema": "public", "extend_existing": True}, # opcional ) - key: Mapped[str] = mapped_column( - String(5), nullable=False - ) # clave del tipo de factura - description: Mapped[str] = mapped_column( - String(50), nullable=False - ) # descripción oficial (en español) - # observación o comentario adicional - note: Mapped[str] = mapped_column(String(500)) - type: Mapped[str] = mapped_column(String(15)) # tipo + key: Mapped[str] = mapped_column(String(5), nullable=False) # clave del tipo de factura + description: Mapped[str] = mapped_column(String(50), nullable=False) # descripción oficial (en español) + note: Mapped[str] = mapped_column(String(500))# observación o comentario adicional + type: Mapped[str] = mapped_column(String(15))# tipo (MATERIAL, fixed asset, both) + operation: Mapped[str] = mapped_column(String(5)) # operación (imp, exp, both) def __repr__(self): - return f"" + return f"" diff --git a/backend/api/v1/modules/public/reference_data/invoice_types/routes.py b/backend/api/v1/modules/public/reference_data/invoice_types/routes.py index 936d7fb3..e0ce0866 100644 --- a/backend/api/v1/modules/public/reference_data/invoice_types/routes.py +++ b/backend/api/v1/modules/public/reference_data/invoice_types/routes.py @@ -1,4 +1,4 @@ -from typing import Any, Dict +from typing import Any, Dict, Optional from core.database import get_core_db from core.security import get_current_user, has_role @@ -11,17 +11,28 @@ from .models import InvoiceType router = APIRouter(prefix="/invoice-types") -@router.get("/", response_model=Dict[str, Any]) +@router.get("/", response_model=dict) def list_invoice_types( - page: int = Query(1, ge=1, description="Número de página"), - page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"), + page: int = Query(1, ge=1), + page_size: int = Query(50, ge=1, le=100), + type: Optional[str] = Query(None, description="Filter by type"), + operation: Optional[str] = Query(None, description="Filter by operation type (imp, exp, both)"), db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), ): - skip = (page - 1) * page_size query = db.query(InvoiceType) - items = query.offset(skip).limit(page_size).all() + + # Filter by operation if provided + if operation: + query = query.filter( + (InvoiceType.operation == operation) | ( + InvoiceType.operation == "both") + ) + + if type == "imp" and operation == "CR": + query = query.filter(InvoiceType.operation != "exp") + total = query.count() + items = query.offset((page - 1) * page_size).limit(page_size).all() return { "items": [InvoiceTypeDTO.model_validate(obj) for obj in items], "total": total, diff --git a/backend/api/v1/modules/public/reference_data/invoice_types/seed.py b/backend/api/v1/modules/public/reference_data/invoice_types/seed.py index dd8f0fcb..151163dd 100644 --- a/backend/api/v1/modules/public/reference_data/invoice_types/seed.py +++ b/backend/api/v1/modules/public/reference_data/invoice_types/seed.py @@ -1,38 +1,76 @@ seed = [ - ("DONAC", "DONACION", "", "AMBOS"), - ("EXDEF", "EXPORTACION DEFINITIVA", "", "MATERIAL"), + # === TIPOS DE IMPORTACION === + ( + "TEM", + "IMPORTACION TEMPORAL", + "IMPORTACION TEMPORAL DE MATERIA PRIMA, COMPONENTES O MATERIALES PARA SER PROCESADOS Y POSTERIORMENTE EXPORTADOS.", + "both", + "imp", + ), + ( + "DEF", + "IMPORTACION DEFINITIVA", + "IMPORTACION DEFINITIVA PARA NACIONALIZACION DE MERCANCIA QUE PERMANECE EN TERRITORIO NACIONAL.", + "both", + "imp", + ), + ( + "MEX", + "COMPRAS MEXICANAS", + "IMPORTACION DE MERCANCIA NACIONAL ADQUIRIDA DE PROVEEDORES MEXICANOS PARA INCORPORAR A PROCESO PRODUCTIVO.", + "both", + "imp", + ), + ( + "CR", + "CAMBIO DE REGIMEN", + "IMPORTACION POR CAMBIO DE REGIMEN DE MERCANCIA TEMPORAL QUE SE NACIONALIZA O RETORNA.", + "both", + "imp", + ), + + # === TIPOS DE EXPORTACION === + ("DONAC", "DONACION", "", "both", "exp"), + ("EXDEF", "EXPORTACION DEFINITIVA", "", "material", "exp"), ( "MATDE", "MATERIA PRIMA O MATERIAL DEVUELTO", "ESTE PROCESO CONSISTE EN SOLO DESCARGAR LAS PARTES DADAS DE ALTA EN MATERIALES QUE SON RETORNADAS SIN NINGUNA MODIFICACION (A1)", - "MATERIAL", + "material", + "exp", ), ( "NODES", "NO HACE DESCARGA", "ESTE PROCESO DE ACTUALIZACION CONSISTE EN EXPORTAR UNA MERCANCIA Y NO DESCARGAR, POR LO TANTO NO EXISTE REPORTE DE DESCARGAS Y NO AFECTA SALDOS.", - "AMBOS", + "both", + "exp", ), ( "PTERM", "PRODUCTO TERMINADO Y VIRTUALES", "EL PRODUCTO TERMINADO Y VIRTUALES DESCARGARAN: 1) APARTIR DE LOS COMPONENTES DE CADA PRODUCTO TERMINADO REGISTRADO EN LAS PARTIDAS DE EXPORTACION. 2) POR PARTE, CON LAS OPCIONES DE PODER DESCARGAR POR SUSTITUTO Y POR CLASE EN CASO DE INSUFICIENCIAS DEL COMPONENTE.", - "MATERIAL", + "material", + "exp", ), ( "REPAR", "REPARACION", "PROCESO QUE CONSISTE EN DOS ETAPAS: 1) DESCARGA EL PRODUCTO DE REPARACION QUE SE IMPORTO PARA REPARA, 2) DESCARGA EL LISTADO DE COMPONENTES QUE SE AGREGO AL PRODUCTO DE REPARACION", - "MATERIAL", + "material", + "exp", ), - ("SCRAP", "SCRAP", "", "AMBOS"), + ("SCRAP", "SCRAP", "", "both", "exp"), ( "VEMEX", "VENTAS EN MEXICO", "ESTE PROCESO CONSISTE EN LA VENTA EN EL MERCADO NACIONAL DE LOS PRODUCTOS.", - "AMBOS", + "both", + "exp", ), - ("VIRTU", "VIRTUALES", "", "MATERIAL"), - ("AFIJO", "ACTIVO FIJO", "", "ACTIVO FIJO"), - ("REEXP", "REEXPEDICION", "", "ACTIVO FIJO"), + ("VIRTU", "VIRTUALES", "", "material", "exp"), + + # === ACTIVOS FIJOS (AMBAS OPERACIONES) === + ("AFIJO", "ACTIVO FIJO", "", "fixed asset", "exp"), + ("REEXP", "REEXPEDICION", "", "fixed asset", "exp"), ] diff --git a/backend/api/v1/router.py b/backend/api/v1/router.py index 5d7dc302..9fd2618e 100644 --- a/backend/api/v1/router.py +++ b/backend/api/v1/router.py @@ -6,6 +6,7 @@ Agrega todos los módulos de la aplicación from fastapi import APIRouter # Importar routers de módulos +from .modules.core.router import router as core_router from .modules.a76.router import router as a76_router from .modules.public.router import router as public_router @@ -13,6 +14,7 @@ from .modules.public.router import router as public_router router = APIRouter() # Registrar módulos +router.include_router(core_router) router.include_router(a76_router) router.include_router(public_router) 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..f71a9aee 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" + - "5050:8000" depends_on: postgres-a76: condition: service_healthy diff --git a/estructura.txt b/estructura.txt new file mode 100644 index 00000000..aa2beccb --- /dev/null +++ b/estructura.txt @@ -0,0 +1,1190 @@ +. +├── azure.crt +├── backend +│   ├── alembic +│   │   ├── env.py +│   │   ├── README +│   │   ├── script.py.mako +│   │   └── versions +│   │   ├── 531bf8cdae06_create_material_types_table.py +│   │   └── 7937209f9718_seed_initial_data.py +│   ├── alembic.ini +│   ├── api +│   │   └── v1 +│   │   ├── common +│   │   │   ├── base_models.py +│   │   │   ├── crud_routes.py +│   │   │   ├── dto_mixins.py +│   │   │   └── tenant_crud_routes.py +│   │   ├── modules +│   │   │   ├── a24 +│   │   │   │   ├── fa +│   │   │   │   │   └── fa_classes +│   │   │   │   │   └── models.py +│   │   │   │   └── inv +│   │   │   │   ├── inv_classes +│   │   │   │   │   └── models.py +│   │   │   │   └── location +│   │   │   │   ├── dto.py +│   │   │   │   ├── __init__.py +│   │   │   │   ├── models.py +│   │   │   │   ├── routes.py +│   │   │   │   └── service.py +│   │   │   ├── a76 +│   │   │   │   ├── classes +│   │   │   │   │   ├── dto.py +│   │   │   │   │   ├── __init__.py +│   │   │   │   │   ├── models.py +│   │   │   │   │   ├── routes.py +│   │   │   │   │   ├── service.py +│   │   │   │   │   └── test_classes.py +│   │   │   │   ├── clients_and_providers +│   │   │   │   │   ├── dto.py +│   │   │   │   │   ├── __init__.py +│   │   │   │   │   ├── models.py +│   │   │   │   │   ├── routes.py +│   │   │   │   │   ├── service.py +│   │   │   │   │   └── test_client_and_provider.py +│   │   │   │   ├── country_rule_oct +│   │   │   │   │   ├── dto.py +│   │   │   │   │   ├── models.py +│   │   │   │   │   ├── routes.py +│   │   │   │   │   ├── services.py +│   │   │   │   │   └── test_country_rule_oct.py +│   │   │   │   ├── customs_brokers +│   │   │   │   │   ├── dto.py +│   │   │   │   │   ├── models.py +│   │   │   │   │   ├── routes.py +│   │   │   │   │   └── services.py +│   │   │   │   ├── fraction_rule_octave +│   │   │   │   │   ├── dto.py +│   │   │   │   │   ├── models.py +│   │   │   │   │   ├── routes.py +│   │   │   │   │   ├── services.py +│   │   │   │   │   └── test_fraction_rule_octave.py +│   │   │   │   ├── general_catalogs +│   │   │   │   │   ├── classification_concepts +│   │   │   │   │   │   ├── dto.py +│   │   │   │   │   │   ├── __init__.py +│   │   │   │   │   │   ├── models.py +│   │   │   │   │   │   ├── routes.py +│   │   │   │   │   │   └── service.py +│   │   │   │   │   ├── company +│   │   │   │   │   │   ├── dto.py +│   │   │   │   │   │   ├── __init__.py +│   │   │   │   │   │   ├── models.py +│   │   │   │   │   │   ├── routes.py +│   │   │   │   │   │   ├── service.py +│   │   │   │   │   │   └── test_company.py +│   │   │   │   │   ├── concepts +│   │   │   │   │   │   ├── dto.py +│   │   │   │   │   │   ├── __init__.py +│   │   │   │   │   │   ├── models.py +│   │   │   │   │   │   ├── routes.py +│   │   │   │   │   │   └── service.py +│   │   │   │   │   ├── customs_broker_concepts +│   │   │   │   │   │   ├── dto.py +│   │   │   │   │   │   ├── __init__.py +│   │   │   │   │   │   ├── models.py +│   │   │   │   │   │   ├── routes.py +│   │   │   │   │   │   └── service.py +│   │   │   │   │   ├── doda +│   │   │   │   │   │   ├── dto.py +│   │   │   │   │   │   ├── __init__.py +│   │   │   │   │   │   ├── models.py +│   │   │   │   │   │   ├── routes.py +│   │   │   │   │   │   └── service.py +│   │   │   │   │   ├── electronic_notices +│   │   │   │   │   │   ├── dto.py +│   │   │   │   │   │   ├── __init__.py +│   │   │   │   │   │   ├── models.py +│   │   │   │   │   │   ├── routes.py +│   │   │   │   │   │   └── service.py +│   │   │   │   │   ├── equivalencies +│   │   │   │   │   │   ├── dto.py +│   │   │   │   │   │   ├── __init__.py +│   │   │   │   │   │   ├── models.py +│   │   │   │   │   │   ├── routes.py +│   │   │   │   │   │   └── service.py +│   │   │   │   │   ├── error_catalogs +│   │   │   │   │   │   ├── dto.py +│   │   │   │   │   │   ├── __init__.py +│   │   │   │   │   │   ├── models.py +│   │   │   │   │   │   ├── routes.py +│   │   │   │   │   │   └── service.py +│   │   │   │   │   ├── exchange_rate +│   │   │   │   │   │   ├── dto.py +│   │   │   │   │   │   ├── models.py +│   │   │   │   │   │   ├── routes.py +│   │   │   │   │   │   ├── services.py +│   │   │   │   │   │   └── test_exchange_rate.py +│   │   │   │   │   ├── identifiers +│   │   │   │   │   │   ├── dto.py +│   │   │   │   │   │   ├── __init__.py +│   │   │   │   │   │   ├── models.py +│   │   │   │   │   │   ├── routes.py +│   │   │   │   │   │   └── service.py +│   │   │   │   │   ├── inpc +│   │   │   │   │   │   ├── dto.py +│   │   │   │   │   │   ├── __init__.py +│   │   │   │   │   │   ├── models.py +│   │   │   │   │   │   ├── routes.py +│   │   │   │   │   │   └── service.py +│   │   │   │   │   ├── legends +│   │   │   │   │   │   ├── dto.py +│   │   │   │   │   │   ├── __init__.py +│   │   │   │   │   │   ├── models.py +│   │   │   │   │   │   ├── routes.py +│   │   │   │   │   │   └── service.py +│   │   │   │   │   ├── multi_currency_types +│   │   │   │   │   │   ├── dto.py +│   │   │   │   │   │   ├── __init__.py +│   │   │   │   │   │   ├── models.py +│   │   │   │   │   │   ├── routes.py +│   │   │   │   │   │   └── service.py +│   │   │   │   │   ├── packages +│   │   │   │   │   │   ├── dto.py +│   │   │   │   │   │   ├── models.py +│   │   │   │   │   │   ├── routes.py +│   │   │   │   │   │   ├── services.py +│   │   │   │   │   │   └── test_package.py +│   │   │   │   │   ├── ports +│   │   │   │   │   │   ├── dto.py +│   │   │   │   │   │   ├── __init__.py +│   │   │   │   │   │   ├── models.py +│   │   │   │   │   │   ├── routes.py +│   │   │   │   │   │   └── service.py +│   │   │   │   │   ├── prevalidators +│   │   │   │   │   │   ├── dto.py +│   │   │   │   │   │   ├── __init__.py +│   │   │   │   │   │   ├── models.py +│   │   │   │   │   │   ├── routes.py +│   │   │   │   │   │   └── service.py +│   │   │   │   │   ├── seal +│   │   │   │   │   │   ├── dto.py +│   │   │   │   │   │   ├── models.py +│   │   │   │   │   │   ├── routes.py +│   │   │   │   │   │   ├── services.py +│   │   │   │   │   │   └── test_seal.py +│   │   │   │   │   ├── signatures +│   │   │   │   │   │   ├── dto.py +│   │   │   │   │   │   ├── __init__.py +│   │   │   │   │   │   ├── models.py +│   │   │   │   │   │   ├── routes.py +│   │   │   │   │   │   └── service.py +│   │   │   │   │   ├── unit_conversions +│   │   │   │   │   │   ├── dto.py +│   │   │   │   │   │   ├── __init__.py +│   │   │   │   │   │   ├── models.py +│   │   │   │   │   │   ├── routes.py +│   │   │   │   │   │   └── service.py +│   │   │   │   │   └── units_of_measure +│   │   │   │   │   ├── dto.py +│   │   │   │   │   ├── __init__.py +│   │   │   │   │   ├── models.py +│   │   │   │   │   ├── routes.py +│   │   │   │   │   └── service.py +│   │   │   │   ├── invoices +│   │   │   │   │   ├── models.py +│   │   │   │   │   ├── routes.py +│   │   │   │   │   ├── schemas.py +│   │   │   │   │   └── services.py +│   │   │   │   ├── parts +│   │   │   │   │   ├── dto.py +│   │   │   │   │   ├── __init__.py +│   │   │   │   │   ├── models.py +│   │   │   │   │   ├── routes.py +│   │   │   │   │   ├── service.py +│   │   │   │   │   └── test_parts.py +│   │   │   │   ├── pedmientos +│   │   │   │   │   ├── dtos +│   │   │   │   │   │   ├── pedimento_config_additional.py +│   │   │   │   │   │   ├── pedimento_config_calculations.py +│   │   │   │   │   │   ├── pedimento_config_parameters.py +│   │   │   │   │   │   ├── pedimento_config_surcharges.py +│   │   │   │   │   │   ├── pedimento_config_update_rectification.py +│   │   │   │   │   │   ├── pedimento_config_updates.py +│   │   │   │   │   │   ├── pedimento_customs_offices.py +│   │   │   │   │   │   ├── pedimento_dates.py +│   │   │   │   │   │   ├── pedimento_decrementables.py +│   │   │   │   │   │   ├── pedimento_incrementables.py +│   │   │   │   │   │   ├── pedimento_indexes.py +│   │   │   │   │   │   ├── pedimento_payments.py +│   │   │   │   │   │   ├── pedimento_rectification_destination.py +│   │   │   │   │   │   ├── pedimento_rectification_origin.py +│   │   │   │   │   │   ├── pedimentos.py +│   │   │   │   │   │   ├── pedimento_transport_means.py +│   │   │   │   │   │   └── pedimento_validation.py +│   │   │   │   │   ├── models +│   │   │   │   │   │   ├── pedimento_config_additional.py +│   │   │   │   │   │   ├── pedimento_config_calculations.py +│   │   │   │   │   │   ├── pedimento_config_parameters.py +│   │   │   │   │   │   ├── pedimento_config_surcharges.py +│   │   │   │   │   │   ├── pedimento_config_update_rectification.py +│   │   │   │   │   │   ├── pedimento_config_updates.py +│   │   │   │   │   │   ├── pedimento_customs_offices.py +│   │   │   │   │   │   ├── pedimento_dates.py +│   │   │   │   │   │   ├── pedimento_decrementables.py +│   │   │   │   │   │   ├── pedimento_incrementables.py +│   │   │   │   │   │   ├── pedimento_indexes.py +│   │   │   │   │   │   ├── pedimento_payments.py +│   │   │   │   │   │   ├── pedimento_rectification_destination.py +│   │   │   │   │   │   ├── pedimento_rectification_origin.py +│   │   │   │   │   │   ├── pedimentos.py +│   │   │   │   │   │   ├── pedimento_transport_means.py +│   │   │   │   │   │   └── pedimento_validation.py +│   │   │   │   │   ├── router.py +│   │   │   │   │   ├── routes +│   │   │   │   │   │   ├── pedimento_config_additional.py +│   │   │   │   │   │   ├── pedimento_config_calculations.py +│   │   │   │   │   │   ├── pedimento_config_parameters.py +│   │   │   │   │   │   ├── pedimento_config_surcharges.py +│   │   │   │   │   │   ├── pedimento_config_update_rectification.py +│   │   │   │   │   │   ├── pedimento_config_updates.py +│   │   │   │   │   │   ├── pedimento_customs_offices.py +│   │   │   │   │   │   ├── pedimento_dates.py +│   │   │   │   │   │   ├── pedimento_decrementables.py +│   │   │   │   │   │   ├── pedimento_incrementables.py +│   │   │   │   │   │   ├── pedimento_indexes.py +│   │   │   │   │   │   ├── pedimento_payments.py +│   │   │   │   │   │   ├── pedimento_rectification_destination.py +│   │   │   │   │   │   ├── pedimento_rectification_origin.py +│   │   │   │   │   │   ├── pedimentos.py +│   │   │   │   │   │   ├── pedimento_transport_means.py +│   │   │   │   │   │   └── pedimento_validation.py +│   │   │   │   │   └── services +│   │   │   │   │   ├── pedimento_config_additional.py +│   │   │   │   │   ├── pedimento_config_calculations.py +│   │   │   │   │   ├── pedimento_config_parameters.py +│   │   │   │   │   ├── pedimento_config_surcharges.py +│   │   │   │   │   ├── pedimento_config_update_rectification.py +│   │   │   │   │   ├── pedimento_config_updates.py +│   │   │   │   │   ├── pedimento_customs_offices.py +│   │   │   │   │   ├── pedimento_dates.py +│   │   │   │   │   ├── pedimento_decrementables.py +│   │   │   │   │   ├── pedimento_incrementables.py +│   │   │   │   │   ├── pedimento_indexes.py +│   │   │   │   │   ├── pedimento_payments.py +│   │   │   │   │   ├── pedimento_rectification_destination.py +│   │   │   │   │   ├── pedimento_rectification_origin.py +│   │   │   │   │   ├── pedimentos.py +│   │   │   │   │   ├── pedimento_transport_means.py +│   │   │   │   │   └── pedimento_validation.py +│   │   │   │   ├── permission_rule_oct +│   │   │   │   │   ├── dto.py +│   │   │   │   │   ├── models.py +│   │   │   │   │   ├── routes.py +│   │   │   │   │   ├── services.py +│   │   │   │   │   └── test_permission_rule_oct.py +│   │   │   │   ├── router.py +│   │   │   │   └── transportation +│   │   │   │   ├── drivers +│   │   │   │   │   ├── dto.py +│   │   │   │   │   ├── models.py +│   │   │   │   │   ├── routes.py +│   │   │   │   │   └── services.py +│   │   │   │   ├── trailers +│   │   │   │   │   ├── dto.py +│   │   │   │   │   ├── models.py +│   │   │   │   │   ├── routes.py +│   │   │   │   │   └── services.py +│   │   │   │   ├── transporters +│   │   │   │   │   ├── dto.py +│   │   │   │   │   ├── models.py +│   │   │   │   │   ├── routes.py +│   │   │   │   │   └── services.py +│   │   │   │   └── vehicles +│   │   │   │   ├── dto.py +│   │   │   │   ├── models.py +│   │   │   │   ├── routes.py +│   │   │   │   └── services.py +│   │   │   ├── core +│   │   │   │   ├── auth +│   │   │   │   │   ├── dto.py +│   │   │   │   │   ├── __init__.py +│   │   │   │   │   ├── routes.py +│   │   │   │   │   └── service.py +│   │   │   │   ├── licenses +│   │   │   │   │   ├── dto.py +│   │   │   │   │   ├── __init__.py +│   │   │   │   │   ├── models.py +│   │   │   │   │   ├── routes.py +│   │   │   │   │   └── service.py +│   │   │   │   ├── router.py +│   │   │   │   ├── tenants +│   │   │   │   │   ├── dto.py +│   │   │   │   │   ├── __init__.py +│   │   │   │   │   ├── models.py +│   │   │   │   │   ├── routes.py +│   │   │   │   │   └── service.py +│   │   │   │   └── user_tenant +│   │   │   │   ├── dto.py +│   │   │   │   ├── models.py +│   │   │   │   ├── routes.py +│   │   │   │   └── service.py +│   │   │   └── public +│   │   │   ├── __init__.py +│   │   │   ├── reference_data +│   │   │   │   ├── code_pedimento_regimens +│   │   │   │   │   ├── dto.py +│   │   │   │   │   ├── models.py +│   │   │   │   │   ├── routes.py +│   │   │   │   │   ├── seed.py +│   │   │   │   │   └── test_code_pedimento_regimens.py +│   │   │   │   ├── conftest.py +│   │   │   │   ├── containers +│   │   │   │   │   ├── dto.py +│   │   │   │   │   ├── models.py +│   │   │   │   │   ├── routes.py +│   │   │   │   │   ├── seed.py +│   │   │   │   │   └── test_containers.py +│   │   │   │   ├── countries +│   │   │   │   │   ├── dto.py +│   │   │   │   │   ├── models.py +│   │   │   │   │   ├── routes.py +│   │   │   │   │   ├── seed.py +│   │   │   │   │   └── test_countries.py +│   │   │   │   ├── currency_types +│   │   │   │   │   ├── dto.py +│   │   │   │   │   ├── models.py +│   │   │   │   │   ├── routes.py +│   │   │   │   │   ├── seed.py +│   │   │   │   │   └── test_currency_types.py +│   │   │   │   ├── customs_sections +│   │   │   │   │   ├── dto.py +│   │   │   │   │   ├── models.py +│   │   │   │   │   ├── routes.py +│   │   │   │   │   ├── seed.py +│   │   │   │   │   └── test_customs_sections.py +│   │   │   │   ├── customs_warehouses +│   │   │   │   │   ├── dto.py +│   │   │   │   │   ├── models.py +│   │   │   │   │   ├── routes.py +│   │   │   │   │   ├── seed.py +│   │   │   │   │   └── test_customs_warehouses.py +│   │   │   │   ├── incoterms +│   │   │   │   │   ├── dto.py +│   │   │   │   │   ├── models.py +│   │   │   │   │   ├── routes.py +│   │   │   │   │   ├── seed.py +│   │   │   │   │   └── test_incoterms.py +│   │   │   │   ├── invoice_types +│   │   │   │   │   ├── dto.py +│   │   │   │   │   ├── models.py +│   │   │   │   │   ├── routes.py +│   │   │   │   │   ├── seed.py +│   │   │   │   │   └── test_invoice_types.py +│   │   │   │   ├── material_types +│   │   │   │   │   ├── dto.py +│   │   │   │   │   ├── models.py +│   │   │   │   │   ├── routes.py +│   │   │   │   │   ├── seed.py +│   │   │   │   │   └── test_material_types.py +│   │   │   │   ├── payment_methods +│   │   │   │   │   ├── dto.py +│   │   │   │   │   ├── models.py +│   │   │   │   │   ├── routes.py +│   │   │   │   │   ├── seed.py +│   │   │   │   │   └── test_payment_methods.py +│   │   │   │   ├── pedimento_codes +│   │   │   │   │   ├── dto.py +│   │   │   │   │   ├── models.py +│   │   │   │   │   ├── routes.py +│   │   │   │   │   ├── seed.py +│   │   │   │   │   └── test_pedimento_codes.py +│   │   │   │   ├── pedimento_regimens +│   │   │   │   │   ├── dto.py +│   │   │   │   │   ├── models.py +│   │   │   │   │   ├── routes.py +│   │   │   │   │   ├── seed.py +│   │   │   │   │   └── test_pedimento_regimens.py +│   │   │   │   ├── router.py +│   │   │   │   ├── sectors +│   │   │   │   │   ├── dto.py +│   │   │   │   │   ├── models.py +│   │   │   │   │   ├── routes.py +│   │   │   │   │   ├── seed.py +│   │   │   │   │   └── test_sectors.py +│   │   │   │   ├── states +│   │   │   │   │   ├── dto.py +│   │   │   │   │   ├── models.py +│   │   │   │   │   ├── routes.py +│   │   │   │   │   ├── seed.py +│   │   │   │   │   └── test_states.py +│   │   │   │   ├── trailer_types +│   │   │   │   │   ├── dto.py +│   │   │   │   │   ├── models.py +│   │   │   │   │   ├── routes.py +│   │   │   │   │   └── services.py +│   │   │   │   ├── transport_modes +│   │   │   │   │   ├── dto.py +│   │   │   │   │   ├── models.py +│   │   │   │   │   ├── routes.py +│   │   │   │   │   ├── seed.py +│   │   │   │   │   └── test_transport_modes.py +│   │   │   │   ├── transport_types +│   │   │   │   │   ├── dto.py +│   │   │   │   │   ├── models.py +│   │   │   │   │   ├── routes.py +│   │   │   │   │   ├── seed.py +│   │   │   │   │   └── test_transport_types.py +│   │   │   │   └── valuation_methods +│   │   │   │   ├── dto.py +│   │   │   │   ├── models.py +│   │   │   │   ├── routes.py +│   │   │   │   ├── seed.py +│   │   │   │   └── test_valuation_methods.py +│   │   │   └── router.py +│   │   └── router.py +│   ├── core +│   │   ├── config.py +│   │   ├── database.py +│   │   ├── __init__.py +│   │   ├── middleware.py +│   │   └── security.py +│   ├── Dockerfile +│   ├── main.py +│   ├── __pycache__ +│   └── requirements.txt +├── docker-compose.yml +├── docs +│   ├── a76.json +│   ├── ARCHITECTURE.md +│   ├── KEYCLOAK_SETUP.md +│   ├── MICROSOFT_SSO_SETUP.md +│   └── VERIFICAR_MICROSOFT_CONFIG.md +├── estructura.txt +├── frontend +│   ├── components.json +│   ├── Dockerfile +│   ├── e2e +│   │   └── demo.test.ts +│   ├── eslint.config.js +│   ├── messages +│   │   ├── en.json +│   │   └── es.json +│   ├── node_modules +│   ├── package.json +│   ├── playwright.config.ts +│   ├── pnpm-lock.yaml +│   ├── pnpm-workspace.yaml +│   ├── project.inlang +│   │   ├── cache +│   │   │   └── plugins +│   │   │   ├── 2sy648wh9sugi +│   │   │   └── ygx0uiahq6uw +│   │   ├── project_id +│   │   └── settings.json +│   ├── README.md +│   ├── src +│   │   ├── app.css +│   │   ├── app.d.ts +│   │   ├── app.html +│   │   ├── demo.spec.ts +│   │   ├── hooks.server.ts +│   │   ├── hooks.ts +│   │   ├── lib +│   │   │   ├── api +│   │   │   │   └── dashboard +│   │   │   │   ├── a76 +│   │   │   │   │   ├── classes.ts +│   │   │   │   │   ├── clients-providers.ts +│   │   │   │   │   ├── customs-brokers.ts +│   │   │   │   │   ├── general_catalogs +│   │   │   │   │   │   ├── classification-concepts.ts +│   │   │   │   │   │   ├── company.ts +│   │   │   │   │   │   ├── concepts.ts +│   │   │   │   │   │   ├── customs-broker-concepts.ts +│   │   │   │   │   │   ├── doda.ts +│   │   │   │   │   │   ├── electronic-notices.ts +│   │   │   │   │   │   ├── equivalencies.ts +│   │   │   │   │   │   ├── error-catalogs.ts +│   │   │   │   │   │   ├── exchange-rate.ts +│   │   │   │   │   │   ├── identifiers.ts +│   │   │   │   │   │   ├── index.ts +│   │   │   │   │   │   ├── inpc.ts +│   │   │   │   │   │   ├── legends.ts +│   │   │   │   │   │   ├── locations.ts +│   │   │   │   │   │   ├── multi-currency-types.ts +│   │   │   │   │   │   ├── packages.ts +│   │   │   │   │   │   ├── ports.ts +│   │   │   │   │   │   ├── prevalidators.ts +│   │   │   │   │   │   ├── seal.ts +│   │   │   │   │   │   ├── signatures.ts +│   │   │   │   │   │   ├── um-ace.ts +│   │   │   │   │   │   ├── um-customs-ame.ts +│   │   │   │   │   │   ├── um-customs-mex.ts +│   │   │   │   │   │   ├── um-oma.ts +│   │   │   │   │   │   ├── unit-conversions.ts +│   │   │   │   │   │   ├── unit-measures.ts +│   │   │   │   │   │   └── units-of-measure.ts +│   │   │   │   │   ├── index.ts +│   │   │   │   │   ├── invoices.ts +│   │   │   │   │   ├── pedimento-dates.ts +│   │   │   │   │   ├── pedimento-payments.ts +│   │   │   │   │   ├── pedimentos.ts +│   │   │   │   │   ├── pedimento-transport.ts +│   │   │   │   │   └── pedimento-validation.ts +│   │   │   │   └── refrence_data +│   │   │   │   ├── code_pedimento_regimens.ts +│   │   │   │   ├── containers.ts +│   │   │   │   ├── countries.ts +│   │   │   │   ├── currency_types.ts +│   │   │   │   ├── customs_sections.ts +│   │   │   │   ├── customs_warehouses.ts +│   │   │   │   ├── incoterms.ts +│   │   │   │   ├── invoice_types.ts +│   │   │   │   ├── material_types.ts +│   │   │   │   ├── payment_methods.ts +│   │   │   │   ├── pedimento_codes.ts +│   │   │   │   ├── pedimento_regimens.ts +│   │   │   │   ├── sectors.ts +│   │   │   │   ├── states.ts +│   │   │   │   ├── transport_modes.ts +│   │   │   │   ├── transport_types.ts +│   │   │   │   └── valuation_methods.ts +│   │   │   ├── api.ts +│   │   │   ├── assets +│   │   │   │   └── favicon.svg +│   │   │   ├── auth.ts +│   │   │   ├── components +│   │   │   │   ├── dashboard +│   │   │   │   │   ├── classes +│   │   │   │   │   │   ├── columns.ts +│   │   │   │   │   │   ├── create-edit-dialog.svelte +│   │   │   │   │   │   ├── data-table-actions.svelte +│   │   │   │   │   │   └── data-table.svelte +│   │   │   │   │   ├── clients_and_providers +│   │   │   │   │   │   ├── columns.ts +│   │   │   │   │   │   ├── create-edit-dialog.svelte +│   │   │   │   │   │   ├── data-table-actions.svelte +│   │   │   │   │   │   ├── data-table.svelte +│   │   │   │   │   │   ├── delete-dialog.svelte +│   │   │   │   │   │   └── details-dialog.svelte +│   │   │   │   │   ├── company +│   │   │   │   │   │   ├── columns.ts +│   │   │   │   │   │   ├── create-edit-dialog.svelte +│   │   │   │   │   │   └── data-table-actions.svelte +│   │   │   │   │   ├── customs_brokers +│   │   │   │   │   │   ├── columns.ts +│   │   │   │   │   │   ├── create-dialog.svelte +│   │   │   │   │   │   ├── data-table-actions.svelte +│   │   │   │   │   │   ├── data-table.svelte +│   │   │   │   │   │   ├── delete-dialog.svelte +│   │   │   │   │   │   ├── details-dialog.svelte +│   │   │   │   │   │   └── edit-dialog.svelte +│   │   │   │   │   ├── exchange-rate +│   │   │   │   │   │   ├── columns.ts +│   │   │   │   │   │   ├── create-edit-dialog.svelte +│   │   │   │   │   │   ├── data-table-actions.svelte +│   │   │   │   │   │   └── data-table.svelte +│   │   │   │   │   ├── general_catalogs +│   │   │   │   │   │   └── simple-data-table.svelte +│   │   │   │   │   ├── identifiers +│   │   │   │   │   │   ├── columns.ts +│   │   │   │   │   │   ├── create-edit-dialog.svelte +│   │   │   │   │   │   ├── data-table-actions.svelte +│   │   │   │   │   │   └── data-table.svelte +│   │   │   │   │   ├── invoices +│   │   │   │   │   │   ├── columns.ts +│   │   │   │   │   │   ├── create-edit-dialog.svelte +│   │   │   │   │   │   ├── data-table-actions.svelte +│   │   │   │   │   │   ├── data-table.svelte +│   │   │   │   │   │   ├── delete-dialog.svelte +│   │   │   │   │   │   └── details-dialog.svelte +│   │   │   │   │   ├── locations +│   │   │   │   │   │   ├── columns.ts +│   │   │   │   │   │   └── data-table.svelte +│   │   │   │   │   ├── packages +│   │   │   │   │   │   ├── columns.ts +│   │   │   │   │   │   ├── create-edit-dialog.svelte +│   │   │   │   │   │   ├── data-table-actions.svelte +│   │   │   │   │   │   └── data-table.svelte +│   │   │   │   │   ├── pedimentos +│   │   │   │   │   │   ├── columns.ts +│   │   │   │   │   │   ├── data-table-actions.svelte +│   │   │   │   │   │   ├── data-table.svelte +│   │   │   │   │   │   └── edit +│   │   │   │   │   │   ├── dates-tab-form.svelte +│   │   │   │   │   │   ├── general-tab-form.svelte +│   │   │   │   │   │   ├── payments-tab-form.svelte +│   │   │   │   │   │   ├── transport-tab-form.svelte +│   │   │   │   │   │   └── validation-tab-form.svelte +│   │   │   │   │   ├── ports +│   │   │   │   │   │   ├── columns.ts +│   │   │   │   │   │   ├── create-edit-dialog.svelte +│   │   │   │   │   │   └── data-table-actions.svelte +│   │   │   │   │   ├── reference_data +│   │   │   │   │   │   ├── code_pedimento_regimens +│   │   │   │   │   │   │   ├── columns.ts +│   │   │   │   │   │   │   ├── create-edit-dialog.svelte +│   │   │   │   │   │   │   ├── data-table-actions.svelte +│   │   │   │   │   │   │   ├── data-table.svelte +│   │   │   │   │   │   │   ├── delete-dialog.svelte +│   │   │   │   │   │   │   └── details-dialog.svelte +│   │   │   │   │   │   ├── containers +│   │   │   │   │   │   │   ├── columns.ts +│   │   │   │   │   │   │   ├── create-edit-dialog.svelte +│   │   │   │   │   │   │   ├── data-table-actions.svelte +│   │   │   │   │   │   │   ├── data-table.svelte +│   │   │   │   │   │   │   ├── delete-dialog.svelte +│   │   │   │   │   │   │   └── details-dialog.svelte +│   │   │   │   │   │   ├── countries +│   │   │   │   │   │   │   ├── columns.ts +│   │   │   │   │   │   │   ├── create-edit-dialog.svelte +│   │   │   │   │   │   │   ├── data-table-actions.svelte +│   │   │   │   │   │   │   ├── data-table.svelte +│   │   │   │   │   │   │   ├── delete-dialog.svelte +│   │   │   │   │   │   │   └── details-dialog.svelte +│   │   │   │   │   │   ├── currency_types +│   │   │   │   │   │   │   ├── columns.ts +│   │   │   │   │   │   │   ├── create-edit-dialog.svelte +│   │   │   │   │   │   │   ├── data-table-actions.svelte +│   │   │   │   │   │   │   ├── data-table.svelte +│   │   │   │   │   │   │   ├── delete-dialog.svelte +│   │   │   │   │   │   │   └── details-dialog.svelte +│   │   │   │   │   │   ├── customs_sections +│   │   │   │   │   │   │   ├── columns.ts +│   │   │   │   │   │   │   ├── create-edit-dialog.svelte +│   │   │   │   │   │   │   ├── data-table-actions.svelte +│   │   │   │   │   │   │   ├── data-table.svelte +│   │   │   │   │   │   │   ├── delete-dialog.svelte +│   │   │   │   │   │   │   └── details-dialog.svelte +│   │   │   │   │   │   ├── customs_warehouses +│   │   │   │   │   │   │   ├── columns.ts +│   │   │   │   │   │   │   ├── create-edit-dialog.svelte +│   │   │   │   │   │   │   ├── data-table-actions.svelte +│   │   │   │   │   │   │   ├── data-table.svelte +│   │   │   │   │   │   │   ├── delete-dialog.svelte +│   │   │   │   │   │   │   └── details-dialog.svelte +│   │   │   │   │   │   ├── incoterms +│   │   │   │   │   │   │   ├── columns.ts +│   │   │   │   │   │   │   ├── create-edit-dialog.svelte +│   │   │   │   │   │   │   ├── data-table-actions.svelte +│   │   │   │   │   │   │   ├── data-table.svelte +│   │   │   │   │   │   │   ├── delete-dialog.svelte +│   │   │   │   │   │   │   └── details-dialog.svelte +│   │   │   │   │   │   ├── invoice_types +│   │   │   │   │   │   │   ├── columns.ts +│   │   │   │   │   │   │   ├── create-edit-dialog.svelte +│   │   │   │   │   │   │   ├── data-table-actions.svelte +│   │   │   │   │   │   │   ├── data-table.svelte +│   │   │   │   │   │   │   ├── delete-dialog.svelte +│   │   │   │   │   │   │   └── details-dialog.svelte +│   │   │   │   │   │   ├── material_types +│   │   │   │   │   │   │   ├── columns.ts +│   │   │   │   │   │   │   ├── create-edit-dialog.svelte +│   │   │   │   │   │   │   ├── data-table-actions.svelte +│   │   │   │   │   │   │   ├── data-table.svelte +│   │   │   │   │   │   │   ├── delete-dialog.svelte +│   │   │   │   │   │   │   └── details-dialog.svelte +│   │   │   │   │   │   ├── payment_methods +│   │   │   │   │   │   │   ├── columns.ts +│   │   │   │   │   │   │   ├── create-edit-dialog.svelte +│   │   │   │   │   │   │   ├── data-table-actions.svelte +│   │   │   │   │   │   │   ├── data-table.svelte +│   │   │   │   │   │   │   ├── delete-dialog.svelte +│   │   │   │   │   │   │   └── details-dialog.svelte +│   │   │   │   │   │   ├── pedimento_codes +│   │   │   │   │   │   │   ├── columns.ts +│   │   │   │   │   │   │   ├── create-edit-dialog.svelte +│   │   │   │   │   │   │   ├── data-table-actions.svelte +│   │   │   │   │   │   │   ├── data-table.svelte +│   │   │   │   │   │   │   ├── delete-dialog.svelte +│   │   │   │   │   │   │   └── details-dialog.svelte +│   │   │   │   │   │   ├── pedimento_regimens +│   │   │   │   │   │   │   ├── columns.ts +│   │   │   │   │   │   │   ├── create-edit-dialog.svelte +│   │   │   │   │   │   │   ├── data-table-actions.svelte +│   │   │   │   │   │   │   ├── data-table.svelte +│   │   │   │   │   │   │   ├── delete-dialog.svelte +│   │   │   │   │   │   │   └── details-dialog.svelte +│   │   │   │   │   │   ├── sectors +│   │   │   │   │   │   │   ├── columns.ts +│   │   │   │   │   │   │   ├── create-edit-dialog.svelte +│   │   │   │   │   │   │   ├── data-table-actions.svelte +│   │   │   │   │   │   │   ├── data-table.svelte +│   │   │   │   │   │   │   ├── delete-dialog.svelte +│   │   │   │   │   │   │   └── details-dialog.svelte +│   │   │   │   │   │   ├── states +│   │   │   │   │   │   │   ├── columns.ts +│   │   │   │   │   │   │   ├── create-edit-dialog.svelte +│   │   │   │   │   │   │   ├── data-table-actions.svelte +│   │   │   │   │   │   │   ├── data-table.svelte +│   │   │   │   │   │   │   ├── delete-dialog.svelte +│   │   │   │   │   │   │   └── details-dialog.svelte +│   │   │   │   │   │   ├── transport_modes +│   │   │   │   │   │   │   ├── columns.ts +│   │   │   │   │   │   │   ├── create-edit-dialog.svelte +│   │   │   │   │   │   │   ├── data-table-actions.svelte +│   │   │   │   │   │   │   ├── data-table.svelte +│   │   │   │   │   │   │   ├── delete-dialog.svelte +│   │   │   │   │   │   │   └── details-dialog.svelte +│   │   │   │   │   │   ├── transport_types +│   │   │   │   │   │   │   ├── columns.ts +│   │   │   │   │   │   │   ├── create-edit-dialog.svelte +│   │   │   │   │   │   │   ├── data-table-actions.svelte +│   │   │   │   │   │   │   ├── data-table.svelte +│   │   │   │   │   │   │   ├── delete-dialog.svelte +│   │   │   │   │   │   │   └── details-dialog.svelte +│   │   │   │   │   │   └── valuation_methods +│   │   │   │   │   │   ├── columns.ts +│   │   │   │   │   │   ├── create-edit-dialog.svelte +│   │   │   │   │   │   ├── data-table-actions.svelte +│   │   │   │   │   │   ├── data-table.svelte +│   │   │   │   │   │   ├── delete-dialog.svelte +│   │   │   │   │   │   └── details-dialog.svelte +│   │   │   │   │   ├── seal +│   │   │   │   │   │   ├── columns.ts +│   │   │   │   │   │   ├── create-edit-dialog.svelte +│   │   │   │   │   │   ├── data-table-actions.svelte +│   │   │   │   │   │   ├── data-table.svelte +│   │   │   │   │   │   └── index.ts +│   │   │   │   │   └── units_of_measure +│   │   │   │   │   ├── ace +│   │   │   │   │   │   ├── columns.ts +│   │   │   │   │   │   ├── create-edit-dialog.svelte +│   │   │   │   │   │   ├── data-table-actions.svelte +│   │   │   │   │   │   └── data-table.svelte +│   │   │   │   │   ├── american +│   │   │   │   │   │   ├── columns.ts +│   │   │   │   │   │   ├── create-edit-dialog.svelte +│   │   │   │   │   │   └── data-table-actions.svelte +│   │   │   │   │   ├── customs +│   │   │   │   │   │   ├── columns.ts +│   │   │   │   │   │   ├── create-edit-dialog.svelte +│   │   │   │   │   │   ├── data-table-actions.svelte +│   │   │   │   │   │   └── data-table.svelte +│   │   │   │   │   ├── general +│   │   │   │   │   │   ├── columns.ts +│   │   │   │   │   │   ├── create-edit-dialog.svelte +│   │   │   │   │   │   ├── data-table-actions.svelte +│   │   │   │   │   │   └── data-table.svelte +│   │   │   │   │   └── oma +│   │   │   │   │   ├── columns.ts +│   │   │   │   │   ├── create-edit-dialog.svelte +│   │   │   │   │   └── data-table-actions.svelte +│   │   │   │   ├── login-form.svelte +│   │   │   │   ├── sidebar +│   │   │   │   │   ├── app-sidebar.svelte +│   │   │   │   │   ├── modules.ts +│   │   │   │   │   ├── nav-main.svelte +│   │   │   │   │   ├── nav-projects.svelte +│   │   │   │   │   ├── nav-user.svelte +│   │   │   │   │   └── team-switcher.svelte +│   │   │   │   └── ui +│   │   │   │   ├── alert +│   │   │   │   │   ├── alert-description.svelte +│   │   │   │   │   ├── alert.svelte +│   │   │   │   │   ├── alert-title.svelte +│   │   │   │   │   └── index.ts +│   │   │   │   ├── alert-dialog +│   │   │   │   │   ├── alert-dialog-action.svelte +│   │   │   │   │   ├── alert-dialog-cancel.svelte +│   │   │   │   │   ├── alert-dialog-content.svelte +│   │   │   │   │   ├── alert-dialog-description.svelte +│   │   │   │   │   ├── alert-dialog-footer.svelte +│   │   │   │   │   ├── alert-dialog-header.svelte +│   │   │   │   │   ├── alert-dialog-overlay.svelte +│   │   │   │   │   ├── alert-dialog-title.svelte +│   │   │   │   │   ├── alert-dialog-trigger.svelte +│   │   │   │   │   └── index.ts +│   │   │   │   ├── avatar +│   │   │   │   │   ├── avatar-fallback.svelte +│   │   │   │   │   ├── avatar-image.svelte +│   │   │   │   │   ├── avatar.svelte +│   │   │   │   │   └── index.ts +│   │   │   │   ├── badge +│   │   │   │   │   ├── badge.svelte +│   │   │   │   │   └── index.ts +│   │   │   │   ├── breadcrumb +│   │   │   │   │   ├── breadcrumb-ellipsis.svelte +│   │   │   │   │   ├── breadcrumb-item.svelte +│   │   │   │   │   ├── breadcrumb-link.svelte +│   │   │   │   │   ├── breadcrumb-list.svelte +│   │   │   │   │   ├── breadcrumb-page.svelte +│   │   │   │   │   ├── breadcrumb-separator.svelte +│   │   │   │   │   ├── breadcrumb.svelte +│   │   │   │   │   └── index.ts +│   │   │   │   ├── button +│   │   │   │   │   ├── button.svelte +│   │   │   │   │   └── index.ts +│   │   │   │   ├── card +│   │   │   │   │   ├── card-action.svelte +│   │   │   │   │   ├── card-content.svelte +│   │   │   │   │   ├── card-description.svelte +│   │   │   │   │   ├── card-footer.svelte +│   │   │   │   │   ├── card-header.svelte +│   │   │   │   │   ├── card.svelte +│   │   │   │   │   ├── card-title.svelte +│   │   │   │   │   └── index.ts +│   │   │   │   ├── collapsible +│   │   │   │   │   ├── collapsible-content.svelte +│   │   │   │   │   ├── collapsible.svelte +│   │   │   │   │   ├── collapsible-trigger.svelte +│   │   │   │   │   └── index.ts +│   │   │   │   ├── data-table +│   │   │   │   │   ├── data-table.svelte.ts +│   │   │   │   │   ├── flex-render.svelte +│   │   │   │   │   ├── index.ts +│   │   │   │   │   └── render-helpers.ts +│   │   │   │   ├── dialog +│   │   │   │   │   ├── dialog-close.svelte +│   │   │   │   │   ├── dialog-content.svelte +│   │   │   │   │   ├── dialog-description.svelte +│   │   │   │   │   ├── dialog-footer.svelte +│   │   │   │   │   ├── dialog-header.svelte +│   │   │   │   │   ├── dialog-overlay.svelte +│   │   │   │   │   ├── dialog-title.svelte +│   │   │   │   │   ├── dialog-trigger.svelte +│   │   │   │   │   └── index.ts +│   │   │   │   ├── dropdown-menu +│   │   │   │   │   ├── dropdown-menu-checkbox-item.svelte +│   │   │   │   │   ├── dropdown-menu-content.svelte +│   │   │   │   │   ├── dropdown-menu-group-heading.svelte +│   │   │   │   │   ├── dropdown-menu-group.svelte +│   │   │   │   │   ├── dropdown-menu-item.svelte +│   │   │   │   │   ├── dropdown-menu-label.svelte +│   │   │   │   │   ├── dropdown-menu-radio-group.svelte +│   │   │   │   │   ├── dropdown-menu-radio-item.svelte +│   │   │   │   │   ├── dropdown-menu-separator.svelte +│   │   │   │   │   ├── dropdown-menu-shortcut.svelte +│   │   │   │   │   ├── dropdown-menu-sub-content.svelte +│   │   │   │   │   ├── dropdown-menu-sub-trigger.svelte +│   │   │   │   │   ├── dropdown-menu-trigger.svelte +│   │   │   │   │   └── index.ts +│   │   │   │   ├── field +│   │   │   │   │   ├── field-content.svelte +│   │   │   │   │   ├── field-description.svelte +│   │   │   │   │   ├── field-error.svelte +│   │   │   │   │   ├── field-group.svelte +│   │   │   │   │   ├── field-label.svelte +│   │   │   │   │   ├── field-legend.svelte +│   │   │   │   │   ├── field-separator.svelte +│   │   │   │   │   ├── field-set.svelte +│   │   │   │   │   ├── field.svelte +│   │   │   │   │   ├── field-title.svelte +│   │   │   │   │   └── index.ts +│   │   │   │   ├── input +│   │   │   │   │   ├── index.ts +│   │   │   │   │   └── input.svelte +│   │   │   │   ├── label +│   │   │   │   │   ├── index.ts +│   │   │   │   │   └── label.svelte +│   │   │   │   ├── select +│   │   │   │   │   ├── index.ts +│   │   │   │   │   ├── select-content.svelte +│   │   │   │   │   ├── select-group-heading.svelte +│   │   │   │   │   ├── select-group.svelte +│   │   │   │   │   ├── select-item.svelte +│   │   │   │   │   ├── select-label.svelte +│   │   │   │   │   ├── select-scroll-down-button.svelte +│   │   │   │   │   ├── select-scroll-up-button.svelte +│   │   │   │   │   ├── select-separator.svelte +│   │   │   │   │   └── select-trigger.svelte +│   │   │   │   ├── separator +│   │   │   │   │   ├── index.ts +│   │   │   │   │   └── separator.svelte +│   │   │   │   ├── sheet +│   │   │   │   │   ├── index.ts +│   │   │   │   │   ├── sheet-close.svelte +│   │   │   │   │   ├── sheet-content.svelte +│   │   │   │   │   ├── sheet-description.svelte +│   │   │   │   │   ├── sheet-footer.svelte +│   │   │   │   │   ├── sheet-header.svelte +│   │   │   │   │   ├── sheet-overlay.svelte +│   │   │   │   │   ├── sheet-title.svelte +│   │   │   │   │   └── sheet-trigger.svelte +│   │   │   │   ├── sidebar +│   │   │   │   │   ├── constants.ts +│   │   │   │   │   ├── context.svelte.ts +│   │   │   │   │   ├── index.ts +│   │   │   │   │   ├── sidebar-content.svelte +│   │   │   │   │   ├── sidebar-footer.svelte +│   │   │   │   │   ├── sidebar-group-action.svelte +│   │   │   │   │   ├── sidebar-group-content.svelte +│   │   │   │   │   ├── sidebar-group-label.svelte +│   │   │   │   │   ├── sidebar-group.svelte +│   │   │   │   │   ├── sidebar-header.svelte +│   │   │   │   │   ├── sidebar-input.svelte +│   │   │   │   │   ├── sidebar-inset.svelte +│   │   │   │   │   ├── sidebar-menu-action.svelte +│   │   │   │   │   ├── sidebar-menu-badge.svelte +│   │   │   │   │   ├── sidebar-menu-button.svelte +│   │   │   │   │   ├── sidebar-menu-item.svelte +│   │   │   │   │   ├── sidebar-menu-skeleton.svelte +│   │   │   │   │   ├── sidebar-menu-sub-button.svelte +│   │   │   │   │   ├── sidebar-menu-sub-item.svelte +│   │   │   │   │   ├── sidebar-menu-sub.svelte +│   │   │   │   │   ├── sidebar-menu.svelte +│   │   │   │   │   ├── sidebar-provider.svelte +│   │   │   │   │   ├── sidebar-rail.svelte +│   │   │   │   │   ├── sidebar-separator.svelte +│   │   │   │   │   ├── sidebar.svelte +│   │   │   │   │   └── sidebar-trigger.svelte +│   │   │   │   ├── skeleton +│   │   │   │   │   ├── index.ts +│   │   │   │   │   └── skeleton.svelte +│   │   │   │   ├── switch +│   │   │   │   │   ├── index.ts +│   │   │   │   │   └── switch.svelte +│   │   │   │   ├── table +│   │   │   │   │   ├── index.ts +│   │   │   │   │   ├── table-body.svelte +│   │   │   │   │   ├── table-caption.svelte +│   │   │   │   │   ├── table-cell.svelte +│   │   │   │   │   ├── table-footer.svelte +│   │   │   │   │   ├── table-header.svelte +│   │   │   │   │   ├── table-head.svelte +│   │   │   │   │   ├── table-row.svelte +│   │   │   │   │   └── table.svelte +│   │   │   │   ├── tabs +│   │   │   │   │   ├── index.ts +│   │   │   │   │   ├── tabs-content.svelte +│   │   │   │   │   ├── tabs-list.svelte +│   │   │   │   │   ├── tabs.svelte +│   │   │   │   │   └── tabs-trigger.svelte +│   │   │   │   ├── textarea +│   │   │   │   │   ├── index.ts +│   │   │   │   │   └── textarea.svelte +│   │   │   │   └── tooltip +│   │   │   │   ├── index.ts +│   │   │   │   ├── tooltip-content.svelte +│   │   │   │   └── tooltip-trigger.svelte +│   │   │   ├── hooks +│   │   │   │   └── is-mobile.svelte.ts +│   │   │   ├── paraglide +│   │   │   │   ├── messages +│   │   │   │   │   ├── en.js +│   │   │   │   │   ├── es.js +│   │   │   │   │   └── _index.js +│   │   │   │   ├── messages.js +│   │   │   │   ├── registry.js +│   │   │   │   ├── runtime.js +│   │   │   │   └── server.js +│   │   │   ├── server +│   │   │   │   └── api.ts +│   │   │   ├── sso.ts +│   │   │   ├── stores +│   │   │   │   └── company.svelte.ts +│   │   │   └── utils.ts +│   │   └── routes +│   │   ├── api +│   │   │   └── company +│   │   │   └── my-companies +│   │   │   └── +server.ts +│   │   ├── auth +│   │   │   └── callback +│   │   │   ├── +page.server.ts +│   │   │   └── +page.svelte +│   │   ├── dashboard +│   │   │   ├── classes +│   │   │   │   └── +page.svelte +│   │   │   ├── clients_and_providers +│   │   │   │   ├── +page.server.ts +│   │   │   │   └── +page.svelte +│   │   │   ├── customs_brokers +│   │   │   │   ├── +page.server.ts +│   │   │   │   └── +page.svelte +│   │   │   ├── general_catalogs +│   │   │   │   ├── classification_concepts +│   │   │   │   │   ├── +page.server.ts +│   │   │   │   │   └── +page.svelte +│   │   │   │   ├── company_information +│   │   │   │   │   ├── +page.server.ts +│   │   │   │   │   └── +page.svelte +│   │   │   │   ├── concepts +│   │   │   │   │   ├── +page.server.ts +│   │   │   │   │   └── +page.svelte +│   │   │   │   ├── customs_broker_concepts +│   │   │   │   │   ├── +page.server.ts +│   │   │   │   │   └── +page.svelte +│   │   │   │   ├── doda +│   │   │   │   │   ├── +page.server.ts +│   │   │   │   │   └── +page.svelte +│   │   │   │   ├── electronic_notices +│   │   │   │   │   ├── +page.server.ts +│   │   │   │   │   └── +page.svelte +│   │   │   │   ├── equivalencies +│   │   │   │   │   ├── +page.server.ts +│   │   │   │   │   └── +page.svelte +│   │   │   │   ├── error_catalogs +│   │   │   │   │   ├── +page.server.ts +│   │   │   │   │   └── +page.svelte +│   │   │   │   ├── exchange-rate +│   │   │   │   │   ├── +page.server.ts +│   │   │   │   │   └── +page.svelte +│   │   │   │   ├── identifiers +│   │   │   │   │   ├── +page.server.ts +│   │   │   │   │   └── +page.svelte +│   │   │   │   ├── inpc +│   │   │   │   │   ├── +page.server.ts +│   │   │   │   │   └── +page.svelte +│   │   │   │   ├── legends +│   │   │   │   │   ├── +page.server.ts +│   │   │   │   │   └── +page.svelte +│   │   │   │   ├── locations +│   │   │   │   │   ├── +page.server.ts +│   │   │   │   │   └── +page.svelte +│   │   │   │   ├── multi_currency_types +│   │   │   │   │   ├── +page.server.ts +│   │   │   │   │   └── +page.svelte +│   │   │   │   ├── packages +│   │   │   │   │   ├── +page.server.ts +│   │   │   │   │   └── +page.svelte +│   │   │   │   ├── +page.svelte +│   │   │   │   ├── ports +│   │   │   │   │   ├── +page.server.ts +│   │   │   │   │   └── +page.svelte +│   │   │   │   ├── prevalidators +│   │   │   │   │   ├── +page.server.ts +│   │   │   │   │   └── +page.svelte +│   │   │   │   ├── seal +│   │   │   │   │   └── +page.svelte +│   │   │   │   ├── signatures +│   │   │   │   │   ├── +page.server.ts +│   │   │   │   │   └── +page.svelte +│   │   │   │   ├── unit_conversions +│   │   │   │   │   ├── +page.server.ts +│   │   │   │   │   └── +page.svelte +│   │   │   │   └── units_of_measure +│   │   │   │   ├── ace +│   │   │   │   │   ├── +page.server.ts +│   │   │   │   │   └── +page.svelte +│   │   │   │   ├── american +│   │   │   │   │   ├── +page.server.ts +│   │   │   │   │   └── +page.svelte +│   │   │   │   ├── customs +│   │   │   │   │   ├── +page.server.ts +│   │   │   │   │   └── +page.svelte +│   │   │   │   ├── general +│   │   │   │   │   ├── +page.server.ts +│   │   │   │   │   └── +page.svelte +│   │   │   │   └── oma +│   │   │   │   ├── +page.server.ts +│   │   │   │   └── +page.svelte +│   │   │   ├── invoices +│   │   │   │   ├── exportacion +│   │   │   │   │   ├── exportacion +│   │   │   │   │   │   ├── +page.server.ts +│   │   │   │   │   │   └── +page.svelte +│   │   │   │   │   └── reparacion +│   │   │   │   │   ├── +page.server.ts +│   │   │   │   │   └── +page.svelte +│   │   │   │   └── importacion +│   │   │   │   ├── cambio_regimen +│   │   │   │   │   ├── new +│   │   │   │   │   │   └── +page.svelte +│   │   │   │   │   ├── +page.server.ts +│   │   │   │   │   └── +page.svelte +│   │   │   │   ├── compras_mexicanas +│   │   │   │   │   ├── new +│   │   │   │   │   │   └── +page.svelte +│   │   │   │   │   ├── +page.server.ts +│   │   │   │   │   └── +page.svelte +│   │   │   │   ├── definitiva +│   │   │   │   │   ├── new +│   │   │   │   │   │   └── +page.svelte +│   │   │   │   │   ├── +page.server.ts +│   │   │   │   │   └── +page.svelte +│   │   │   │   └── temporal +│   │   │   │   ├── new +│   │   │   │   │   └── +page.svelte +│   │   │   │   ├── +page.server.ts +│   │   │   │   └── +page.svelte +│   │   │   ├── +layout.server.ts +│   │   │   ├── +layout.svelte +│   │   │   ├── +layout.ts +│   │   │   ├── +page.svelte +│   │   │   ├── pedimentos +│   │   │   │   ├── edit +│   │   │   │   │   └── [id] +│   │   │   │   │   ├── +page.server.ts +│   │   │   │   │   └── +page.svelte +│   │   │   │   ├── +page.server.ts +│   │   │   │   └── +page.svelte +│   │   │   └── reference_data +│   │   │   ├── code_pedimento_regimens +│   │   │   │   ├── +page.server.ts +│   │   │   │   └── +page.svelte +│   │   │   ├── containers +│   │   │   │   ├── +page.server.ts +│   │   │   │   └── +page.svelte +│   │   │   ├── countries +│   │   │   │   ├── +page.server.ts +│   │   │   │   └── +page.svelte +│   │   │   ├── currency_types +│   │   │   │   ├── +page.server.ts +│   │   │   │   └── +page.svelte +│   │   │   ├── customs_sections +│   │   │   │   ├── +page.server.ts +│   │   │   │   └── +page.svelte +│   │   │   ├── customs_warehouses +│   │   │   │   ├── +page.server.ts +│   │   │   │   └── +page.svelte +│   │   │   ├── incoterms +│   │   │   │   ├── +page.server.ts +│   │   │   │   └── +page.svelte +│   │   │   ├── invoice_types +│   │   │   │   ├── +page.server.ts +│   │   │   │   └── +page.svelte +│   │   │   ├── material_types +│   │   │   │   ├── +page.server.ts +│   │   │   │   └── +page.svelte +│   │   │   ├── +page.svelte +│   │   │   ├── payment_methods +│   │   │   │   ├── +page.server.ts +│   │   │   │   └── +page.svelte +│   │   │   ├── pedimento_codes +│   │   │   │   ├── +page.server.ts +│   │   │   │   └── +page.svelte +│   │   │   ├── pedimento_regimens +│   │   │   │   ├── +page.server.ts +│   │   │   │   └── +page.svelte +│   │   │   ├── sectors +│   │   │   │   ├── +page.server.ts +│   │   │   │   └── +page.svelte +│   │   │   ├── states +│   │   │   │   ├── +page.server.ts +│   │   │   │   └── +page.svelte +│   │   │   ├── transport_modes +│   │   │   │   ├── +page.server.ts +│   │   │   │   └── +page.svelte +│   │   │   ├── transport_types +│   │   │   │   ├── +page.server.ts +│   │   │   │   └── +page.svelte +│   │   │   └── valuation_methods +│   │   │   ├── +page.server.ts +│   │   │   └── +page.svelte +│   │   ├── demo +│   │   │   ├── +page.svelte +│   │   │   └── paraglide +│   │   │   └── +page.svelte +│   │   ├── +layout.svelte +│   │   ├── login +│   │   │   ├── +page.server.ts +│   │   │   └── +page.svelte +│   │   ├── logout +│   │   │   └── +server.ts +│   │   ├── +page.server.ts +│   │   ├── +page.svelte +│   │   ├── page.svelte.spec.ts +│   │   └── register +│   │   └── +page.svelte +│   ├── svelte.config.js +│   ├── tsconfig.json +│   ├── vite.config.ts +│   └── vitest-setup-client.ts +├── pnpm-lock.yaml +├── README.md +├── scripts +│   ├── backend-entrypoint.sh +│   ├── frontend-entrypoint.sh +│   ├── health-check.sh +│   ├── init_first_time.sh +│   ├── keycloak-entrypoint.sh +│   ├── postgres-app-entrypoint.sh +│   └── postgres-keycloak-entrypoint.sh +└── start.sh + +245 directories, 943 files diff --git a/frontend/messages/en.json b/frontend/messages/en.json index 2e0e9afd..d4274cd5 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -41,11 +41,11 @@ "valuation_methods": "Valuation Methods", "countries": "Countries", "ports": "Ports", - "unit_measures": "Unit Measures", - "um_customs_mex": "U.M. Customs Mexico", - "um_customs_ame": "U.M. Customs America", - "um_ace": "U.M. ACE", - "um_oma": "U.M. OMA", + "unit_measures": "Units of Measure - General", + "um_customs_mex": "Units of Measure - Mexican Customs", + "um_customs_ame": "Units of Measure - American Customs", + "um_ace": "Units of Measure - ACE", + "um_oma": "Units of Measure - OMA", "conversions": "Conversions", "equivalences": "Equivalences", "exchange_rates": "Exchange Rates", @@ -72,6 +72,18 @@ "customs_sections": "Customs Sections", "anexo_22_app_31": "Anexo 22 App 3" }, + "import_invoices":{ + "title": "Import Invoices", + "temporary": "Temporary", + "definitive": "Definitive", + "mexican_purchases": "Mexican Purchases", + "regime_change": "Regime Change" + }, + "export_invoices": { + "title": "Export Invoices", + "exportation": "Exportation", + "repair": "Repair" + }, "clients_and_providers": "Clients and Providers", "customs_brokers": "Customs Brokers", "nav_user": { diff --git a/frontend/messages/es.json b/frontend/messages/es.json index ae2806e5..e4b9728e 100644 --- a/frontend/messages/es.json +++ b/frontend/messages/es.json @@ -41,11 +41,11 @@ "valuation_methods": "Metódos de valoración", "countries": "Países", "ports": "Puertos", - "unit_measures": "Unidades de medida", - "um_customs_mex": "U.M. Aduanas Mexicanas", - "um_customs_ame": "U.M. Aduanas Americanas", - "um_ace": "U.M. ACE", - "um_oma": "U.M. OMA", + "unit_measures": "Unidades de medida general", + "um_customs_mex": "Unidades de medida - Aduanas Mexicanas", + "um_customs_ame": "Unidades de medida - Aduanas Americanas", + "um_ace": "Unidades de medida - ACE", + "um_oma": "Unidades de medida - OMA", "conversions": "Conversiones", "equivalences": "Equivalencias", "exchange_rates": "Tipos de cambio", @@ -72,6 +72,18 @@ "customs_sections": "Secciones Aduaneras", "anexo_22_app_31": "Anexo 22 App 3" }, + "import_invoices":{ + "title": "Facturas de importación", + "temporary": "Temporal", + "definitive": "Definitiva", + "mexican_purchases": "Compras mexicanas", + "regime_change": "Cambio de régimen" + }, + "export_invoices": { + "title": "Facturas de exportación", + "exportation": "Exportación", + "repair": "Reparación" + }, "clients_and_providers": "Clientes y Proveedores", "customs_brokers": "Agentes Aduanales", "nav_user": { diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 8bc86a87..5221c628 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -194,6 +194,14 @@ async function fetchApi( } } + // Manejar respuestas sin contenido (204 No Content) + if (response.status === 204) { + return { + data: null as T, + status: response.status + }; + } + const data = await response.json(); if (!response.ok) { diff --git a/frontend/src/lib/api/dashboard/a76/customs-brokers.ts b/frontend/src/lib/api/dashboard/a76/customs-brokers.ts index 17a82200..149b97b5 100644 --- a/frontend/src/lib/api/dashboard/a76/customs-brokers.ts +++ b/frontend/src/lib/api/dashboard/a76/customs-brokers.ts @@ -1,7 +1,4 @@ -/** - * API Client para Agentes Aduanales (Customs Brokers) - * Gestiona las operaciones CRUD para agentes aduanales - */ + import { api } from '$lib/api'; export interface CustomsBroker { diff --git a/frontend/src/lib/api/dashboard/a76/general_catalogs/classification-concepts.ts b/frontend/src/lib/api/dashboard/a76/general_catalogs/classification-concepts.ts index 6179c188..f87a5871 100644 --- a/frontend/src/lib/api/dashboard/a76/general_catalogs/classification-concepts.ts +++ b/frontend/src/lib/api/dashboard/a76/general_catalogs/classification-concepts.ts @@ -2,60 +2,63 @@ import { api } from '$lib/api'; import type { ApiResponse } from '$lib/api'; export interface ClassificationConcept { - id: number; - classification: string; - description?: string; - tenant_id: string; - company_id?: string; - created_at: string; - updated_at?: string; + id: number; + classification: string; + tenant_id: number; + company_id: number; + created_at: string; + updated_at?: string; } export interface ClassificationConceptCreate { - classification: string; - description?: string; + classification: string; } export interface ClassificationConceptUpdate extends Partial {} export interface ClassificationConceptListResponse { - items: ClassificationConcept[]; - total: number; - page: number; - page_size: number; - pages: number; + items: ClassificationConcept[]; + total: number; + page: number; + page_size: number; + pages: number; } export async function getClassificationConcepts( - 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/classification_concepts?${params.toString()}`); - return response.data; + return await api.get(`/v1/a76/classification-concepts/?${params.toString()}`); } -export async function getClassificationConcept(id: number): Promise { - const response = await api.get(`/a76/classification_concepts/${id}`); - return response.data; +export async function getClassificationConcept(id: number, companyId: number): Promise> { + return await api.get(`/v1/a76/classification-concepts/${id}/?company_id=${companyId}`); } -export async function createClassificationConcept(data: ClassificationConceptCreate): Promise { - const response = await api.post('/a76/classification_concepts', data); - return response.data; +export async function createClassificationConcept( + data: ClassificationConceptCreate, + companyId: number +): Promise> { + return await api.post(`/v1/a76/classification-concepts/?company_id=${companyId}`, data); } -export async function updateClassificationConcept(id: number, data: ClassificationConceptUpdate): Promise { - const response = await api.patch(`/a76/classification_concepts/${id}`, data); - return response.data; +export async function updateClassificationConcept( + id: number, + data: ClassificationConceptUpdate, + companyId: number +): Promise> { + return await api.put(`/v1/a76/classification-concepts/${id}/?company_id=${companyId}`, data); } -export async function deleteClassificationConcept(id: number): Promise { - await api.delete(`/a76/classification_concepts/${id}`); -} +export async function deleteClassificationConcept(id: number, companyId: number): Promise> { + return await api.delete(`/v1/a76/classification-concepts/${id}/?company_id=${companyId}`); +} \ No newline at end of file diff --git a/frontend/src/lib/api/dashboard/a76/general_catalogs/company.ts b/frontend/src/lib/api/dashboard/a76/general_catalogs/company.ts index 3806c386..d31e6aba 100644 --- a/frontend/src/lib/api/dashboard/a76/general_catalogs/company.ts +++ b/frontend/src/lib/api/dashboard/a76/general_catalogs/company.ts @@ -6,6 +6,7 @@ export interface Company { tenant_id: number; name: string | null; rfc: string | null; + curp?: string | null; main_activity: string | null; program: string | null; program_number: string | null; @@ -17,6 +18,13 @@ export interface Company { responsible_name: string | null; responsible_last_name: string | null; responsible_mother_last_name: string | null; + responsible_rfc?: string | null; + position?: string | null; + has_express_line?: boolean; + is_service_company?: boolean; + order_format_type?: string | null; + ctpat_svi?: string | null; + trusted_exporter_number?: string | null; created_at: string | null; updated_at: string | null; } @@ -24,6 +32,7 @@ export interface Company { export interface CompanyCreate { name?: string | null; rfc?: string | null; + curp?: string | null; main_activity?: string | null; program?: string | null; program_number?: string | null; @@ -35,11 +44,19 @@ export interface CompanyCreate { responsible_name?: string | null; responsible_last_name?: string | null; responsible_mother_last_name?: string | null; + responsible_rfc?: string | null; + position?: string | null; + has_express_line?: boolean; + is_service_company?: boolean; + order_format_type?: string | null; + ctpat_svi?: string | null; + trusted_exporter_number?: string | null; } export interface CompanyUpdate { name?: string | null; rfc?: string | null; + curp?: string | null; main_activity?: string | null; program?: string | null; program_number?: string | null; @@ -51,6 +68,13 @@ export interface CompanyUpdate { responsible_name?: string | null; responsible_last_name?: string | null; responsible_mother_last_name?: string | null; + responsible_rfc?: string | null; + position?: string | null; + has_express_line?: boolean; + is_service_company?: boolean; + order_format_type?: string | null; + ctpat_svi?: string | null; + trusted_exporter_number?: string | null; } export interface CompanyListResponse { @@ -71,21 +95,21 @@ export async function getCompanies( page_size: pageSize.toString(), ...filters }); - return await api.get(`/a76/company?${queryParams.toString()}`); + return await api.get(`/v1/a76/company?${queryParams.toString()}`); } export async function getCompany(id: number): Promise> { - return await api.get(`/a76/company/${id}`); + return await api.get(`/v1/a76/company/${id}`); } export async function createCompany(data: CompanyCreate): Promise> { - return await api.post(`/a76/company`, data); + return await api.post(`/v1/a76/company`, data); } export async function updateCompany(id: number, data: CompanyUpdate): Promise> { - return await api.put(`/a76/company/${id}`, data); + return await api.put(`/v1/a76/company/${id}`, data); } export async function deleteCompany(id: number): Promise> { - return await api.delete(`/a76/company/${id}`); + return await api.delete(`/v1/a76/company/${id}`); } diff --git a/frontend/src/lib/api/dashboard/a76/general_catalogs/concepts.ts b/frontend/src/lib/api/dashboard/a76/general_catalogs/concepts.ts index aa723d72..86a58061 100644 --- a/frontend/src/lib/api/dashboard/a76/general_catalogs/concepts.ts +++ b/frontend/src/lib/api/dashboard/a76/general_catalogs/concepts.ts @@ -2,78 +2,80 @@ import { api } from '$lib/api'; import type { ApiResponse } from '$lib/api'; export interface Concept { - id: number; - code: string; - description?: string; - description_en?: string; - detailed_description?: string; - priority?: number; - priority_ame?: number; - first_total?: boolean; - type?: string; - is_printed?: boolean; - section?: number; - classification?: string; - tenant_id: string; - company_id?: string; - created_at: string; - updated_at?: string; + id: number; + code: string; + description?: string; + description_en?: string; + detailed_description?: string; + priority?: number; + priority_ame?: number; + first_total?: boolean; + type?: string; + is_printed?: boolean; + section?: number; + classification?: string; + tenant_id: string; + company_id?: string; + created_at: string; + updated_at?: string; } export interface ConceptCreate { - code: string; - description?: string; - description_en?: string; - detailed_description?: string; - priority?: number; - priority_ame?: number; - first_total?: boolean; - type?: string; - is_printed?: boolean; - section?: number; - classification?: string; + code: string; + description?: string; + description_en?: string; + detailed_description?: string; + priority?: number; + priority_ame?: number; + first_total?: boolean; + type?: string; + is_printed?: boolean; + section?: number; + classification?: string; } export interface ConceptUpdate extends Partial {} export interface ConceptListResponse { - items: Concept[]; - total: number; - page: number; - page_size: number; - pages: number; + items: Concept[]; + total: number; + page: number; + page_size: number; + pages: number; } export async function getConcepts( - 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/concepts?${params.toString()}`); - return response.data; + return await api.get(`/v1/a76/concepts?${params.toString()}`); } -export async function getConcept(id: number): Promise { - const response = await api.get(`/a76/concepts/${id}`); - return response.data; + +export async function getConcept(id: number, companyId: number): Promise> { + return await api.get(`/v1/a76/concepts/${id}?company_id=${companyId}`); } -export async function createConcept(data: ConceptCreate): Promise { - const response = await api.post('/a76/concepts', data); - return response.data; + +export async function createConcept(data: ConceptCreate, companyId: number): Promise> { + return await api.post(`/v1/a76/concepts?company_id=${companyId}`, data); } -export async function updateConcept(id: number, data: ConceptUpdate): Promise { - const response = await api.patch(`/a76/concepts/${id}`, data); - return response.data; + +export async function updateConcept(id: number, data: ConceptUpdate, companyId: number): Promise> { + return await api.put(`/v1/a76/concepts/${id}?company_id=${companyId}`, data); } -export async function deleteConcept(id: number): Promise { - await api.delete(`/a76/concepts/${id}`); -} + +export async function deleteConcept(id: number, companyId: number): Promise> { + return await api.delete(`/v1/a76/concepts/${id}?company_id=${companyId}`); +} \ No newline at end of file diff --git a/frontend/src/lib/api/dashboard/a76/general_catalogs/customs-broker-concepts.ts b/frontend/src/lib/api/dashboard/a76/general_catalogs/customs-broker-concepts.ts index 5e296fb8..e9cf0b07 100644 --- a/frontend/src/lib/api/dashboard/a76/general_catalogs/customs-broker-concepts.ts +++ b/frontend/src/lib/api/dashboard/a76/general_catalogs/customs-broker-concepts.ts @@ -2,76 +2,74 @@ import { api } from '$lib/api'; import type { ApiResponse } from '$lib/api'; export interface CustomsBrokerConcept { - id: number; - code: string; - description?: string; - description_en?: string; - detailed_description?: string; - priority?: number; - first_total?: boolean; - type?: string; - is_printed?: boolean; - section?: number; - classification?: string; - tenant_id: string; - company_id?: string; - created_at: string; - updated_at?: string; + id: number; + code: string; + description?: string; + description_en?: string; + detailed_description?: string; + priority?: number; + first_total?: boolean; + type?: string; + is_printed?: boolean; + section?: number; + classification?: string; + tenant_id: string; + company_id?: string; + created_at: string; + updated_at?: string; } export interface CustomsBrokerConceptCreate { - code: string; - description?: string; - description_en?: string; - detailed_description?: string; - priority?: number; - first_total?: boolean; - type?: string; - is_printed?: boolean; - section?: number; - classification?: string; + code: string; + description?: string; + description_en?: string; + detailed_description?: string; + priority?: number; + first_total?: boolean; + type?: string; + is_printed?: boolean; + section?: number; + classification?: string; } export interface CustomsBrokerConceptUpdate extends Partial {} export interface CustomsBrokerConceptListResponse { - items: CustomsBrokerConcept[]; - total: number; - page: number; - page_size: number; - pages: number; + items: CustomsBrokerConcept[]; + total: number; + page: number; + page_size: number; + pages: number; } export async function getCustomsBrokerConcepts( - 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/customs_broker_concepts?${params.toString()}`); - return response.data; + return await api.get(`/v1/a76/customs-broker-concepts?${params.toString()}`); } -export async function getCustomsBrokerConcept(id: number): Promise { - const response = await api.get(`/a76/customs_broker_concepts/${id}`); - return response.data; +export async function getCustomsBrokerConcept(id: number, companyId: number): Promise> { + return await api.get(`/v1/a76/customs-broker-concepts/${id}?company_id=${companyId}`); } -export async function createCustomsBrokerConcept(data: CustomsBrokerConceptCreate): Promise { - const response = await api.post('/a76/customs_broker_concepts', data); - return response.data; +export async function createCustomsBrokerConcept(data: CustomsBrokerConceptCreate, companyId: number): Promise> { + return await api.post(`/v1/a76/customs-broker-concepts?company_id=${companyId}`, data); } -export async function updateCustomsBrokerConcept(id: number, data: CustomsBrokerConceptUpdate): Promise { - const response = await api.patch(`/a76/customs_broker_concepts/${id}`, data); - return response.data; +export async function updateCustomsBrokerConcept(id: number, data: CustomsBrokerConceptUpdate, companyId: number): Promise> { + return await api.put(`/v1/a76/customs-broker-concepts/${id}?company_id=${companyId}`, data); } -export async function deleteCustomsBrokerConcept(id: number): Promise { - await api.delete(`/a76/customs_broker_concepts/${id}`); +export async function deleteCustomsBrokerConcept(id: number, companyId: number): Promise> { + return await api.delete(`/v1/a76/customs-broker-concepts/${id}?company_id=${companyId}`); } diff --git a/frontend/src/lib/api/dashboard/a76/general_catalogs/customs_broker_concepts.ts b/frontend/src/lib/api/dashboard/a76/general_catalogs/customs_broker_concepts.ts new file mode 100644 index 00000000..e9cf0b07 --- /dev/null +++ b/frontend/src/lib/api/dashboard/a76/general_catalogs/customs_broker_concepts.ts @@ -0,0 +1,75 @@ +import { api } from '$lib/api'; +import type { ApiResponse } from '$lib/api'; + +export interface CustomsBrokerConcept { + id: number; + code: string; + description?: string; + description_en?: string; + detailed_description?: string; + priority?: number; + first_total?: boolean; + type?: string; + is_printed?: boolean; + section?: number; + classification?: string; + tenant_id: string; + company_id?: string; + created_at: string; + updated_at?: string; +} + +export interface CustomsBrokerConceptCreate { + code: string; + description?: string; + description_en?: string; + detailed_description?: string; + priority?: number; + first_total?: boolean; + type?: string; + is_printed?: boolean; + section?: number; + classification?: string; +} + +export interface CustomsBrokerConceptUpdate extends Partial {} + +export interface CustomsBrokerConceptListResponse { + items: CustomsBrokerConcept[]; + total: number; + page: number; + page_size: number; + pages: number; +} + +export async function getCustomsBrokerConcepts( + page: number = 1, + pageSize: number = 50, + companyId: number, + filters: Record = {}, +): Promise> { + const params = new URLSearchParams({ + page: page.toString(), + page_size: pageSize.toString(), + company_id: companyId.toString(), + ...filters + }); + + return await api.get(`/v1/a76/customs-broker-concepts?${params.toString()}`); +} + +export async function getCustomsBrokerConcept(id: number, companyId: number): Promise> { + return await api.get(`/v1/a76/customs-broker-concepts/${id}?company_id=${companyId}`); +} + +export async function createCustomsBrokerConcept(data: CustomsBrokerConceptCreate, companyId: number): Promise> { + return await api.post(`/v1/a76/customs-broker-concepts?company_id=${companyId}`, data); +} + +export async function updateCustomsBrokerConcept(id: number, data: CustomsBrokerConceptUpdate, companyId: number): Promise> { + return await api.put(`/v1/a76/customs-broker-concepts/${id}?company_id=${companyId}`, data); +} + +export async function deleteCustomsBrokerConcept(id: number, companyId: number): Promise> { + return await api.delete(`/v1/a76/customs-broker-concepts/${id}?company_id=${companyId}`); +} 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 74ce4983..45d57304 100644 --- a/frontend/src/lib/api/dashboard/a76/general_catalogs/doda.ts +++ b/frontend/src/lib/api/dashboard/a76/general_catalogs/doda.ts @@ -1,61 +1,186 @@ import { api } from '$lib/api'; import type { ApiResponse } from '$lib/api'; -export interface DODA { - id: number; - code: string; - description?: string; - tenant_id: string; - company_id?: string; - created_at: string; - updated_at?: string; + +export interface DodaContainerSeal { + id: number; + doda_sys_id: number; + seal_line: number; + seal_value?: string; } -export interface DODACreate { - code: string; - description?: string; +export interface DodaContainerSealCreate { + seal_value?: string; } -export interface DODAUpdate extends Partial {} - -export interface DODAListResponse { - items: DODA[]; - total: number; - page: number; - page_size: number; - pages: number; +export interface DodaContainer { + id: number; + doda_sys_id: number; + container_line: number; + container_value?: string; + seals?: string; + seals_detail?: DodaContainerSeal[]; } -export async function getDODAs( - page: number = 1, - pageSize: number = 50, - filters: Record = {} -): Promise> { - const params = new URLSearchParams({ - page: page.toString(), - page_size: pageSize.toString(), - ...filters - }); - - const response = await api.get(`/a76/doda?${params.toString()}`); - return response.data; +export interface DodaContainerCreate { + container_value?: string; + seals?: string; + seals_detail?: DodaContainerSealCreate[]; } -export async function getDODA(id: number): Promise { - const response = await api.get(`/a76/doda/${id}`); - return response.data; + +export interface DodaAmericanPedimento { + id: number; + doda_sys_id: number; + american_pedimento_line: number; + american_pedimento_type?: string; + american_pedimento_value?: string; } -export async function createDODA(data: DODACreate): Promise { - const response = await api.post('/a76/doda', data); - return response.data; +export interface DodaAmericanPedimentoCreate { + american_pedimento_type?: string; + american_pedimento_value?: string; } -export async function updateDODA(id: number, data: DODAUpdate): Promise { - const response = await api.patch(`/a76/doda/${id}`, data); - return response.data; +export interface DodaPedimento { + id: number; + doda_sys_id: number; + pedimento_line: number; + authorization_patent?: string; + document?: string; + shipment?: string; + cove?: string; + umc?: string; + effective_amount_usd?: number; + difference_amount_usd?: number; + dta_niu?: string; + article_7?: boolean; + pedimento_sys_id?: number; + invoice_line?: number; + part_ii_line?: number; + pedimento_type?: string; + zero_packaging_validation?: boolean; } -export async function deleteDODA(id: number): Promise { - await api.delete(`/a76/doda/${id}`); +export interface DodaPedimentoCreate { + authorization_patent?: string; + document?: string; + shipment?: string; + + effective_amount_usd?: number; } + + +export interface Doda { + id: number; + + integration_number?: string; + doda_date?: number; + doda_time?: number; + dispatch_customs?: string; + customs_sections?: string; + patent?: string; + pedimentos?: string; + caat?: string; + transport_identification?: string; + fast_id?: string; + operation_type?: string; + status?: string; + + + containers?: DodaContainer[]; + american_pedimentos?: DodaAmericanPedimento[]; + pedimentos_detail?: DodaPedimento[]; + + + tenant_id?: string; + created_at?: string; + updated_at?: string; +} + + +export interface DodaCreate { + integration_number?: string; + doda_date?: number; + doda_time?: number; + 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 {} + +export interface DodaListResponse { + items: Doda[]; + total: number; + page: number; + page_size: number; + pages: number; +} + + +export async function getDodas( + page: number = 1, + pageSize: number = 50, + 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, 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, 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, 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, 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 a1bb341f..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 @@ -2,60 +2,89 @@ import { api } from '$lib/api'; import type { ApiResponse } from '$lib/api'; export interface ElectronicNotice { - id: number; - code: string; - description?: string; - tenant_id: string; - company_id?: string; - created_at: string; - updated_at?: string; + id: number; + // Campos del Modelo Python + notice_number?: string; + year?: string; + patent?: string; + pedimento?: string; + file_sent?: string; + file_response?: string; + status?: string; + invoice?: string; + validation_acknowledgment?: string; + fea?: string; + certificate_number?: string; + + // Mixins + tenant_id: number; + company_id: number; + created_at: string; + updated_at?: string; } export interface ElectronicNoticeCreate { - code: string; - description?: string; + notice_number?: string; + year?: string; + patent?: string; + pedimento?: string; + file_sent?: string; + file_response?: string; + status?: string; + invoice?: string; + validation_acknowledgment?: string; + fea?: string; + certificate_number?: string; } export interface ElectronicNoticeUpdate extends Partial {} export interface ElectronicNoticeListResponse { - items: ElectronicNotice[]; - total: number; - page: number; - page_size: number; - pages: number; + items: ElectronicNotice[]; + total: number; + page: number; + page_size: number; + pages: number; } export async function getElectronicNotices( - page: number = 1, - pageSize: number = 50, - filters: Record = {} + page: number = 1, + pageSize: number = 50, + filters: Record = {}, + companyId?: number ): Promise> { - const params = new URLSearchParams({ - page: page.toString(), - page_size: pageSize.toString(), - ...filters - }); + 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/electronic_notices?${params.toString()}`); - return response.data; + 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}`); - return response.data; +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); - return response.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); - return response.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 994caa7f..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,61 +1,145 @@ import { api } from '$lib/api'; -import type { ApiResponse } from '$lib/api'; + +// ========================================== +// ERROR CLASSIFICATION +// ========================================== + +export interface ErrorClassification { + id: number; + code: string; + level?: string; + errors?: ErrorCatalog[]; + tenant_id?: string; + created_at?: string; + updated_at?: string; +} + +export interface ErrorClassificationCreate { + code: string; + level?: string; +} + +export interface ErrorClassificationUpdate { + level?: string; +} + +export interface ErrorClassificationListResponse { + items: ErrorClassification[]; + total: number; + page: number; + page_size: number; + pages: number; +} + export interface ErrorCatalog { - id: number; - code: string; - description?: string; - tenant_id: string; - company_id?: string; - created_at: string; - updated_at?: string; + id: number; + code: string; + description?: string; + classification_id?: number; + classification?: ErrorClassification; + tenant_id?: string; + created_at?: string; + updated_at?: string; } export interface ErrorCatalogCreate { - code: string; - description?: string; + code: string; + description?: string; + classification_id?: number; } -export interface ErrorCatalogUpdate extends Partial {} +export interface ErrorCatalogUpdate { + description?: string; + classification_id?: number; +} export interface ErrorCatalogListResponse { - items: ErrorCatalog[]; - total: number; - page: number; - page_size: number; - pages: number; + items: ErrorCatalog[]; + total: number; + page: number; + page_size: number; + pages: number; } +export async function getErrorClassifications( + companyId: number, + page: number = 1, + pageSize: number = 50, + filters: Record = {} +): Promise { + const params = new URLSearchParams({ + page: page.toString(), + page_size: pageSize.toString(), + company_id: companyId.toString(), + ...filters + }); + + const response = await api.get(`/v1/a76/error-catalogs/classifications/?${params.toString()}`); + return response.data; +} + +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, 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, 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, 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( - page: number = 1, - pageSize: number = 50, - filters: Record = {} -): Promise> { - const params = new URLSearchParams({ - page: page.toString(), - page_size: pageSize.toString(), - ...filters - }); + companyId: number, + page: number = 1, + pageSize: number = 50, + filters: Record = {} +): 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()}`); - return response.data; + 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}`); - return response.data; +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); - return response.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); - return response.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/identifiers.ts b/frontend/src/lib/api/dashboard/a76/general_catalogs/identifiers.ts index 5a2ca8d3..98660069 100644 --- a/frontend/src/lib/api/dashboard/a76/general_catalogs/identifiers.ts +++ b/frontend/src/lib/api/dashboard/a76/general_catalogs/identifiers.ts @@ -7,8 +7,8 @@ export interface Identifier { description: string | null; level: string | null; complement: string | null; - company_id: number; - tenant_id: number; + company_id: number; + tenant_id: number; created_at: string | null; updated_at: string | null; } @@ -18,7 +18,6 @@ export interface IdentifierCreate { description?: string | null; level?: string | null; complement?: string | null; - company_id: number; } export interface IdentifierUpdate { @@ -39,24 +38,37 @@ export interface IdentifierListResponse { export async function getIdentifiers( page = 1, pageSize = 50, + companyId: number, filters: Record = {} ): Promise> { const queryParams = new URLSearchParams({ page: page.toString(), page_size: pageSize.toString(), + company_id: companyId.toString(), ...filters }); - return await api.get(`/a76/identifiers?${queryParams.toString()}`); + + return await api.get(`/v1/a76/identifiers/?${queryParams.toString()}`); } -export async function createIdentifier(data: IdentifierCreate): Promise> { - return await api.post('/a76/identifiers', data); +export async function createIdentifier( + data: IdentifierCreate, + companyId: number +): Promise> { + return await api.post(`/v1/a76/identifiers/?company_id=${companyId}`, data); } -export async function updateIdentifier(id: number, data: IdentifierUpdate): Promise> { - return await api.put(`/a76/identifiers/${id}`, data); +export async function updateIdentifier( + id: number, + data: IdentifierUpdate, + companyId: number +): Promise> { + return await api.put(`/v1/a76/identifiers/${id}/?company_id=${companyId}`, data); } -export async function deleteIdentifier(id: number): Promise> { - return await api.delete(`/a76/identifiers/${id}`); -} +export async function deleteIdentifier( + id: number, + companyId: number +): Promise> { + return await api.delete(`/v1/a76/identifiers/${id}/?company_id=${companyId}`); +} \ 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 95f5d8c4..4720519b 100644 --- a/frontend/src/lib/api/dashboard/a76/general_catalogs/inpc.ts +++ b/frontend/src/lib/api/dashboard/a76/general_catalogs/inpc.ts @@ -2,62 +2,67 @@ import { api } from '$lib/api'; import type { ApiResponse } from '$lib/api'; export interface INPC { - id: number; - year: string; - month: string; - value?: number; - tenant_id: string; - company_id?: string; - created_at: string; - updated_at?: string; + id: number; + year: string; + month: string; + value?: number; + tenant_id: number; + company_id: number; + created_at: string; + updated_at?: string; } export interface INPCCreate { - year: string; - month: string; - value?: number; + year: string; + month: string; + value?: number; } export interface INPCUpdate extends Partial {} export interface INPCListResponse { - items: INPC[]; - total: number; - page: number; - page_size: number; - pages: number; + items: INPC[]; + total: number; + page: number; + page_size: number; + pages: number; } export async function getINPCs( - 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/inpc?${params.toString()}`); - return response.data; + return await api.get(`/v1/a76/inpc/?${params.toString()}`); } -export async function getINPC(id: number): Promise { - const response = await api.get(`/a76/inpc/${id}`); - 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): Promise { - const response = await api.post('/a76/inpc', data); - return response.data; +export async function createINPC( + data: INPCCreate, + companyId: number +): Promise> { + return await api.post(`/v1/a76/inpc/?company_id=${companyId}`, data); } -export async function updateINPC(id: number, data: INPCUpdate): Promise { - const response = await api.patch(`/a76/inpc/${id}`, data); - return response.data; +export async function updateINPC( + id: number, + data: INPCUpdate, + companyId: number +): Promise> { + return await api.put(`/v1/a76/inpc/${id}/?company_id=${companyId}`, data); } -export async function deleteINPC(id: number): Promise { - await api.delete(`/a76/inpc/${id}`); +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/legends.ts b/frontend/src/lib/api/dashboard/a76/general_catalogs/legends.ts index d04317a6..1e81cb63 100644 --- a/frontend/src/lib/api/dashboard/a76/general_catalogs/legends.ts +++ b/frontend/src/lib/api/dashboard/a76/general_catalogs/legends.ts @@ -2,60 +2,64 @@ import { api } from '$lib/api'; import type { ApiResponse } from '$lib/api'; export interface Legend { - id: number; - code: string; - description?: string; - tenant_id: string; - company_id?: string; - created_at: string; - updated_at?: string; + id: number; + code: number; + description?: string; + tenant_id: number; + company_id: number; + created_at: string; + updated_at?: string; } export interface LegendCreate { - code: string; - description?: string; + code: number; + description?: string; } export interface LegendUpdate extends Partial {} export interface LegendListResponse { - items: Legend[]; - total: number; - page: number; - page_size: number; - pages: number; + items: Legend[]; + total: number; + page: number; + page_size: number; + pages: number; } export async function getLegends( - 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 response = await api.get(`/a76/legends?${params.toString()}`); - return response.data; + const params = new URLSearchParams({ + page: page.toString(), + page_size: pageSize.toString(), + company_id: companyId.toString(), + ...filters + }); + return await api.get(`/v1/a76/legends/?${params.toString()}`); } -export async function getLegend(id: number): Promise { - const response = await api.get(`/a76/legends/${id}`); - return response.data; +export async function getLegend(id: number, companyId: number): Promise> { + return await api.get(`/v1/a76/legends/${id}/?company_id=${companyId}`); } -export async function createLegend(data: LegendCreate): Promise { - const response = await api.post('/a76/legends', data); - return response.data; +export async function createLegend( + data: LegendCreate, + companyId: number +): Promise> { + return await api.post(`/v1/a76/legends/?company_id=${companyId}`, data); } -export async function updateLegend(id: number, data: LegendUpdate): Promise { - const response = await api.patch(`/a76/legends/${id}`, data); - return response.data; +export async function updateLegend( + id: number, + data: LegendUpdate, + companyId: number +): Promise> { + return await api.put(`/v1/a76/legends/${id}/?company_id=${companyId}`, data); } -export async function deleteLegend(id: number): Promise { - await api.delete(`/a76/legends/${id}`); -} +export async function deleteLegend(id: number, companyId: number): Promise> { + return await api.delete(`/v1/a76/legends/${id}/?company_id=${companyId}`); +} \ No newline at end of file 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 888e6f98..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,52 +1,90 @@ -/** - * API Client para Locations - Ubicaciones relacionadas con puertos - * Basado en los campos location_code y location_description del módulo de puertos - */ +import type { PaginatedResponse } from '$lib/types'; import { api } from '$lib/api'; -import type { ApiResponse } from '$lib/api'; export interface Location { - location_code: string; - location_description: string | null; + id: number; + location_code: string; + location_description: string | null; + company_id: number; + tenant_id: number; } -/** - * Nota: Las ubicaciones están integradas en el módulo de puertos. - * Este archivo proporciona tipos para trabajar con ubicaciones, - * pero las operaciones se realizan a través del módulo de puertos. - * - * Ver: /a76/ports para operaciones relacionadas con ubicaciones - */ - -/** - * Obtiene ubicaciones únicas de los puertos - * Esta función extrae las ubicaciones únicas de la lista de puertos - */ -export async function getLocationsFromPorts(): Promise> { - const portsResponse = await api.get('/a76/ports?page_size=1000'); - - if (portsResponse.error || !portsResponse.data) { - return { - error: portsResponse.error || 'Error al obtener puertos', - status: portsResponse.status - }; - } - - // Extraer ubicaciones únicas - const locationMap = new Map(); - const ports = portsResponse.data.items || []; - - ports.forEach((port: any) => { - if (port.location_code && !locationMap.has(port.location_code)) { - locationMap.set(port.location_code, { - location_code: port.location_code, - location_description: port.location_description - }); - } - }); - - return { - data: Array.from(locationMap.values()), - status: 200 - }; +export interface LocationCreate { + location_code: string; + location_description?: string | null; } + +export interface LocationUpdate { + location_description?: string | null; +} + +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( + companyId: number, + filters?: LocationFilters +): Promise { + const params = new URLSearchParams({ company_id: companyId.toString() }); + + 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( + 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, + 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( + 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( + 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/multi-currency-types.ts b/frontend/src/lib/api/dashboard/a76/general_catalogs/multi-currency-types.ts index d944b8c7..a624e361 100644 --- a/frontend/src/lib/api/dashboard/a76/general_catalogs/multi-currency-types.ts +++ b/frontend/src/lib/api/dashboard/a76/general_catalogs/multi-currency-types.ts @@ -1,61 +1,78 @@ import { api } from '$lib/api'; -import type { ApiResponse } from '$lib/api'; +import type { PaginatedResponse } from '$lib/types'; export interface MultiCurrencyType { id: number; - key: string; - description?: string; - tenant_id: string; - company_id?: string; - created_at: string; - updated_at?: string; + currency_type_code: string; + country_key: string | null; + conversion_factor: number | null; + publication_date: number; + company_id: number; + tenant_id: number; } export interface MultiCurrencyTypeCreate { - key: string; - description?: string; + currency_type_code: string; + country_key?: string | null; + conversion_factor?: number | null; + publication_date: number; } -export interface MultiCurrencyTypeUpdate extends Partial {} +export interface MultiCurrencyTypeUpdate { + currency_type_code?: string; + country_key?: string | null; + conversion_factor?: number | null; + publication_date?: number; +} -export interface MultiCurrencyTypeListResponse { +export interface MultiCurrencyTypeListResponse extends PaginatedResponse { items: MultiCurrencyType[]; - total: number; - page: number; - page_size: number; - pages: number; } export async function getMultiCurrencyTypes( - page: number = 1, - pageSize: number = 50, - filters: Record = {} -): Promise> { - const params = new URLSearchParams({ - page: page.toString(), - page_size: pageSize.toString(), - ...filters - }); + companyId: number, + page?: number, + pageSize?: number +): Promise { + const params = new URLSearchParams({ company_id: companyId.toString() }); + if (page) params.append('page', page.toString()); + if (pageSize) params.append('page_size', pageSize.toString()); - const response = await api.get(`/a76/multi_currency_types?${params.toString()}`); - return response.data; + return api.get(`/v1/a76/multi-currency-types/?${params.toString()}`); } -export async function getMultiCurrencyType(id: number): Promise { - const response = await api.get(`/a76/multi_currency_types/${id}`); - return response.data; +export async function getMultiCurrencyType( + multiCurrencyTypeId: number, + companyId: number +): Promise { + const params = new URLSearchParams({ company_id: companyId.toString() }); + return api.get(`/v1/a76/multi-currency-types/${multiCurrencyTypeId}?${params.toString()}`); } -export async function createMultiCurrencyType(data: MultiCurrencyTypeCreate): Promise { - const response = await api.post('/a76/multi_currency_types', data); - return response.data; +export async function createMultiCurrencyType( + data: MultiCurrencyTypeCreate, + companyId: number +): Promise { + const params = new URLSearchParams({ company_id: companyId.toString() }); + return api.post(`/v1/a76/multi-currency-types/?${params.toString()}`, data); } -export async function updateMultiCurrencyType(id: number, data: MultiCurrencyTypeUpdate): Promise { - const response = await api.patch(`/a76/multi_currency_types/${id}`, data); - return response.data; +export async function updateMultiCurrencyType( + multiCurrencyTypeId: number, + data: MultiCurrencyTypeUpdate, + companyId: number +): Promise { + const params = new URLSearchParams({ company_id: companyId.toString() }); + return api.put( + `/v1/a76/multi-currency-types/${multiCurrencyTypeId}?${params.toString()}`, + data + ); } -export async function deleteMultiCurrencyType(id: number): Promise { - await api.delete(`/a76/multi_currency_types/${id}`); -} +export async function deleteMultiCurrencyType( + multiCurrencyTypeId: number, + companyId: number +): Promise { + const params = new URLSearchParams({ company_id: companyId.toString() }); + return api.delete(`/v1/a76/multi-currency-types/${multiCurrencyTypeId}?${params.toString()}`); +} \ No newline at end of file diff --git a/frontend/src/lib/api/dashboard/a76/general_catalogs/packages.ts b/frontend/src/lib/api/dashboard/a76/general_catalogs/packages.ts index 7af320b2..13a9783a 100644 --- a/frontend/src/lib/api/dashboard/a76/general_catalogs/packages.ts +++ b/frontend/src/lib/api/dashboard/a76/general_catalogs/packages.ts @@ -4,17 +4,17 @@ import type { ApiResponse } from '$lib/api'; export interface Package { id: number; tenant_id: number; - company_id: number; - key: string; - description_es: string | null; - description_en: string | null; - weight_unit: number | null; - plurals: string | null; - plural_in: string | null; - code_ace: string | null; - code_aamex: string | null; - created_at: string | null; - updated_at: string | null; + company_id: number; + key: string; + description_es: string | null; + description_en: string | null; + weight_unit: number | null; + plurals: string | null; + plural_in: string | null; + code_ace: string | null; + code_aamex: string | null; + created_at: string; + updated_at?: string; } export interface PackageCreate { @@ -26,19 +26,9 @@ export interface PackageCreate { plural_in?: string | null; code_ace?: string | null; code_aamex?: string | null; - company_id: number; } -export interface PackageUpdate { - key?: string; - description_es?: string | null; - description_en?: string | null; - weight_unit?: number | null; - plurals?: string | null; - plural_in?: string | null; - code_ace?: string | null; - code_aamex?: string | null; -} +export interface PackageUpdate extends Partial {} export interface PackageListResponse { items: Package[]; @@ -48,31 +38,44 @@ export interface PackageListResponse { pages: number; } + export async function getPackages( - page = 1, - pageSize = 50, + page: number = 1, + pageSize: number = 50, + companyId: number, // Obligatorio por el Mixin filters: Record = {} ): Promise> { - const queryParams = new URLSearchParams({ + const params = new URLSearchParams({ page: page.toString(), page_size: pageSize.toString(), + company_id: companyId.toString(), ...filters }); - return await api.get(`/a76/packages?${queryParams.toString()}`); + + return await api.get(`/v1/a76/packages?${params.toString()}`); } -export async function getPackage(id: number): Promise> { - return await api.get(`/a76/packages/${id}`); + +export async function getPackage(id: number, companyId: number): Promise> { + return await api.get(`/v1/a76/packages/${id}?company_id=${companyId}`); } -export async function createPackage(data: PackageCreate): Promise> { - return await api.post(`/a76/packages`, data); +export async function createPackage( + data: PackageCreate, + companyId: number +): Promise> { + return await api.post(`/v1/a76/packages?company_id=${companyId}`, data); } -export async function updatePackage(id: number, data: PackageUpdate): Promise> { - return await api.put(`/a76/packages/${id}`, data); + +export async function updatePackage( + id: number, + data: PackageUpdate, + companyId: number +): Promise> { + return await api.put(`/v1/a76/packages/${id}?company_id=${companyId}`, data); } -export async function deletePackage(id: number): Promise> { - return await api.delete(`/a76/packages/${id}`); -} +export async function deletePackage(id: number, companyId: number): Promise> { + return await api.delete(`/v1/a76/packages/${id}?company_id=${companyId}`); +} \ No newline at end of file diff --git a/frontend/src/lib/api/dashboard/a76/general_catalogs/ports.ts b/frontend/src/lib/api/dashboard/a76/general_catalogs/ports.ts index 932c4db1..0234bcf0 100644 --- a/frontend/src/lib/api/dashboard/a76/general_catalogs/ports.ts +++ b/frontend/src/lib/api/dashboard/a76/general_catalogs/ports.ts @@ -45,24 +45,30 @@ export interface PortListResponse { export async function getPorts( page = 1, pageSize = 50, - filters: Record = {} + filters: Record = {}, + companyId?: number ): Promise> { const queryParams = new URLSearchParams({ page: page.toString(), page_size: pageSize.toString(), ...filters }); - return await api.get(`/a76/ports?${queryParams.toString()}`); + + if (companyId) { + queryParams.append('company_id', companyId.toString()); + } + + return await api.get(`/v1/a76/ports?${queryParams.toString()}`); } -export async function createPort(data: PortCreate): Promise> { - return await api.post('/a76/ports', data); +export async function createPort(data: PortCreate, companyId: number): Promise> { + return await api.post(`/v1/a76/ports?company_id=${companyId}`, data); } -export async function updatePort(id: number, data: PortUpdate): Promise> { - return await api.put(`/a76/ports/${id}`, data); +export async function updatePort(id: number, data: PortUpdate, companyId: number): Promise> { + return await api.put(`/v1/a76/ports/${id}?company_id=${companyId}`, data); } -export async function deletePort(id: number): Promise> { - return await api.delete(`/a76/ports/${id}`); +export async function deletePort(id: number, companyId: number): Promise> { + return await api.delete(`/v1/a76/ports/${id}?company_id=${companyId}`); } 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 fde1a562..1041bd8b 100644 --- a/frontend/src/lib/api/dashboard/a76/general_catalogs/prevalidators.ts +++ b/frontend/src/lib/api/dashboard/a76/general_catalogs/prevalidators.ts @@ -2,60 +2,73 @@ import { api } from '$lib/api'; import type { ApiResponse } from '$lib/api'; export interface Prevalidator { - id: number; - code: string; - description?: string; - tenant_id: string; - company_id?: string; - created_at: string; - updated_at?: string; + id: number; + code: string; + description?: string; + customs_prevalidator?: string; + patent_prevalidator?: string; + + tenant_id: number; + company_id: number; + created_at: string; + updated_at?: string; } export interface PrevalidatorCreate { - code: string; - description?: string; + code: string; + description?: string; + customs_prevalidator?: string; + patent_prevalidator?: string; } export interface PrevalidatorUpdate extends Partial {} export interface PrevalidatorListResponse { - items: Prevalidator[]; - total: number; - page: number; - page_size: number; - pages: number; + items: Prevalidator[]; + total: number; + page: number; + page_size: number; + pages: number; } export async function getPrevalidators( - page: number = 1, - pageSize: number = 50, - filters: Record = {} + page: number = 1, + pageSize: number = 50, + filters: Record = {}, + companyId?: number ): Promise> { - const params = new URLSearchParams({ - page: page.toString(), - page_size: pageSize.toString(), - ...filters - }); + 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()}`); - return response.data; + 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}`); - return response.data; +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); - return response.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); - return response.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}`); +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 60073763..3f2bfc7d 100644 --- a/frontend/src/lib/api/dashboard/a76/general_catalogs/signatures.ts +++ b/frontend/src/lib/api/dashboard/a76/general_catalogs/signatures.ts @@ -2,62 +2,83 @@ import { api } from '$lib/api'; import type { ApiResponse } from '$lib/api'; export interface Signature { - id: number; - name: string; - position?: string; - certificate?: string; - tenant_id: string; - company_id?: string; - created_at: string; - updated_at?: string; + id: number; + + code: string; + signature: string | null; + photo_path: string | null; + + + tenant_id: number; + company_id: number; + created_at: string; + updated_at?: string; } export interface SignatureCreate { - name: string; - position?: string; - certificate?: string; + code: string; + signature?: string | null; + photo_path?: string | null; + } export interface SignatureUpdate extends Partial {} export interface SignatureListResponse { - items: Signature[]; - total: number; - page: number; - page_size: number; - pages: number; + items: Signature[]; + total: number; + page: number; + page_size: number; + pages: number; } + export async function getSignatures( - 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/signatures?${params.toString()}`); - return response.data; + + const response = await api.get(`/v1/a76/signatures/?${params.toString()}`); + return response.data; } -export async function getSignature(id: number): Promise { - const response = await api.get(`/a76/signatures/${id}`); - return response.data; + +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; } -export async function createSignature(data: SignatureCreate): Promise { - const response = await api.post('/a76/signatures', data); - return response.data; + +export async function createSignature( + data: SignatureCreate, + 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; } -export async function updateSignature(id: number, data: SignatureUpdate): Promise { - const response = await api.patch(`/a76/signatures/${id}`, data); - return response.data; +export async function updateSignature( + id: number, + data: SignatureUpdate, + 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): Promise { - await api.delete(`/a76/signatures/${id}`); -} +export async function deleteSignature(id: number, companyId: number): Promise { + 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 1494fd76..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 @@ -2,62 +2,80 @@ import { api } from '$lib/api'; import type { ApiResponse } from '$lib/api'; export interface UnitConversion { - id: number; - from_unit_id: number; - to_unit_id: number; - conversion_factor: number; - tenant_id: string; - company_id?: string; - created_at: string; - updated_at?: string; + id: number; + from_unit_code: string; + to_unit_code: string; + conversion_factor: number; + tenant_id: number; // number + company_id: number; // number + created_at: string; + updated_at?: string; } export interface UnitConversionCreate { - from_unit_id: number; - to_unit_id: number; - conversion_factor: number; + from_unit_code: string; // Ej: "KGM" + to_unit_code: string; // Ej: "LBR" + conversion_factor: number; + // company_id va en la URL } export interface UnitConversionUpdate extends Partial {} export interface UnitConversionListResponse { - items: UnitConversion[]; - total: number; - page: number; - page_size: number; - pages: number; + items: UnitConversion[]; + total: number; + page: number; + page_size: number; + pages: number; } + export async function getUnitConversions( - page: number = 1, - pageSize: number = 50, - filters: Record = {} + page: number = 1, + pageSize: number = 50, + companyId: number, // 👈 Obligatorio + 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/unit_conversions?${params.toString()}`); - return response.data; + // Agregamos /v1 y prefijo. + // NOTA: Revisa si en tu router definiste "unit_conversions" o "unit-conversions" + const response = await api.get(`/v1/a76/unit-conversions/?${params.toString()}`); + return response.data; } -export async function getUnitConversion(id: number): Promise { - const response = await api.get(`/a76/unit_conversions/${id}`); - return response.data; +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; } -export async function createUnitConversion(data: UnitConversionCreate): Promise { - const response = await api.post('/a76/unit_conversions', data); - return response.data; +export async function createUnitConversion( + data: UnitConversionCreate, + 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; } -export async function updateUnitConversion(id: number, data: UnitConversionUpdate): Promise { - const response = await api.patch(`/a76/unit_conversions/${id}`, data); - return response.data; + +export async function updateUnitConversion( + id: number, + data: UnitConversionUpdate, + 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): Promise { - await api.delete(`/a76/unit_conversions/${id}`); -} +export async function deleteUnitConversion(id: number, companyId: number): Promise { + 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/api/dashboard/a76/general_catalogs/units-of-measure.ts b/frontend/src/lib/api/dashboard/a76/general_catalogs/units-of-measure.ts index cdfa50e4..2aa553ca 100644 --- a/frontend/src/lib/api/dashboard/a76/general_catalogs/units-of-measure.ts +++ b/frontend/src/lib/api/dashboard/a76/general_catalogs/units-of-measure.ts @@ -31,26 +31,28 @@ export interface UnitOfMeasureACEListResponse { export async function getUnitsOfMeasureACE( page = 1, pageSize = 50, + companyId: number, filters: Record = {} ): Promise> { const queryParams = new URLSearchParams({ page: page.toString(), page_size: pageSize.toString(), + company_id: companyId.toString(), ...filters }); - return await api.get(`/a76/units-of-measure/ace?${queryParams.toString()}`); + return await api.get(`/v1/a76/units-of-measure/ace/?${queryParams.toString()}`); } -export async function createUnitOfMeasureACE(data: UnitOfMeasureACECreate): Promise> { - return await api.post('/a76/units-of-measure/ace', data); +export async function createUnitOfMeasureACE(data: UnitOfMeasureACECreate, companyId: number): Promise> { + return await api.post(`/v1/a76/units-of-measure/ace/?company_id=${companyId}`, data); } -export async function updateUnitOfMeasureACE(id: number, data: UnitOfMeasureACEUpdate): Promise> { - return await api.put(`/a76/units-of-measure/ace/${id}`, data); +export async function updateUnitOfMeasureACE(id: number, data: UnitOfMeasureACEUpdate, companyId: number): Promise> { + return await api.put(`/v1/a76/units-of-measure/ace/${id}/?company_id=${companyId}`, data); } -export async function deleteUnitOfMeasureACE(id: number): Promise> { - return await api.delete(`/a76/units-of-measure/ace/${id}`); +export async function deleteUnitOfMeasureACE(id: number, companyId: number): Promise> { + return await api.delete(`/v1/a76/units-of-measure/ace/${id}/?company_id=${companyId}`); } // --- OMA --- @@ -81,28 +83,30 @@ export interface UnitOfMeasureOMAListResponse { } export async function getUnitsOfMeasureOMA( - page = 1, - pageSize = 50, + page: number = 1, + pageSize: number = 50, + companyId: number, filters: Record = {} ): Promise> { const queryParams = new URLSearchParams({ page: page.toString(), page_size: pageSize.toString(), + company_id: companyId.toString(), ...filters }); - return await api.get(`/a76/units-of-measure/oma?${queryParams.toString()}`); + return await api.get(`/v1/a76/units-of-measure/oma/?${queryParams.toString()}`); } -export async function createUnitOfMeasureOMA(data: UnitOfMeasureOMACreate): Promise> { - return await api.post('/a76/units-of-measure/oma', data); +export async function createUnitOfMeasureOMA(data: UnitOfMeasureOMACreate, companyId: number): Promise> { + return await api.post(`/v1/a76/units-of-measure/oma/?company_id=${companyId}`, data); } -export async function updateUnitOfMeasureOMA(id: number, data: UnitOfMeasureOMAUpdate): Promise> { - return await api.put(`/a76/units-of-measure/oma/${id}`, data); +export async function updateUnitOfMeasureOMA(id: number, data: UnitOfMeasureOMAUpdate, companyId: number): Promise> { + return await api.put(`/v1/a76/units-of-measure/oma/${id}/?company_id=${companyId}`, data); } -export async function deleteUnitOfMeasureOMA(id: number): Promise> { - return await api.delete(`/a76/units-of-measure/oma/${id}`); +export async function deleteUnitOfMeasureOMA(id: number, companyId: number): Promise> { + return await api.delete(`/v1/a76/units-of-measure/oma/${id}/?company_id=${companyId}`); } // --- American --- @@ -133,26 +137,139 @@ export interface UnitOfMeasureAmericanListResponse { } export async function getUnitsOfMeasureAmerican( - page = 1, - pageSize = 50, + page: number = 1, + pageSize: number = 50, + companyId: number, filters: Record = {} ): Promise> { const queryParams = new URLSearchParams({ page: page.toString(), page_size: pageSize.toString(), + company_id: companyId.toString(), ...filters }); - return await api.get(`/a76/units-of-measure/american?${queryParams.toString()}`); + return await api.get(`/v1/a76/units-of-measure/american/?${queryParams.toString()}`); } -export async function createUnitOfMeasureAmerican(data: UnitOfMeasureAmericanCreate): Promise> { - return await api.post('/a76/units-of-measure/american', data); +export async function createUnitOfMeasureAmerican(data: UnitOfMeasureAmericanCreate, companyId: number): Promise> { + return await api.post(`/v1/a76/units-of-measure/american/?company_id=${companyId}`, data); } -export async function updateUnitOfMeasureAmerican(id: number, data: UnitOfMeasureAmericanUpdate): Promise> { - return await api.put(`/a76/units-of-measure/american/${id}`, data); +export async function updateUnitOfMeasureAmerican(id: number, data: UnitOfMeasureAmericanUpdate, companyId: number): Promise> { + return await api.put(`/v1/a76/units-of-measure/american/${id}/?company_id=${companyId}`, data); } -export async function deleteUnitOfMeasureAmerican(id: number): Promise> { - return await api.delete(`/a76/units-of-measure/american/${id}`); +export async function deleteUnitOfMeasureAmerican(id: number, companyId: number): Promise> { + return await api.delete(`/v1/a76/units-of-measure/american/${id}/?company_id=${companyId}`); +} + +// --- General --- +export interface UnitOfMeasureGeneral { + id: number; + code: string; + description: string | null; + created_at: string | null; + updated_at: string | null; +} + +export interface UnitOfMeasureGeneralCreate { + code: string; + description?: string | null; +} + +export interface UnitOfMeasureGeneralUpdate { + code?: string; + description?: string | null; +} + +export interface UnitOfMeasureGeneralListResponse { + items: UnitOfMeasureGeneral[]; + total: number; + page: number; + page_size: number; + pages: number; +} + +export async function getUnitsOfMeasureGeneral( + page = 1, + pageSize = 50, + companyId: number, + filters: Record = {} +): Promise> { + const queryParams = new URLSearchParams({ + page: page.toString(), + page_size: pageSize.toString(), + company_id: companyId.toString(), + ...filters + }); + return await api.get(`/v1/a76/units-of-measure/general/?${queryParams.toString()}`); +} + +export async function createUnitOfMeasureGeneral(data: UnitOfMeasureGeneralCreate, companyId: number): Promise> { + return await api.post(`/v1/a76/units-of-measure/general/?company_id=${companyId}`, data); +} + +export async function updateUnitOfMeasureGeneral(id: number, data: UnitOfMeasureGeneralUpdate, companyId: number): Promise> { + return await api.put(`/v1/a76/units-of-measure/general/${id}/?company_id=${companyId}`, data); +} + +export async function deleteUnitOfMeasureGeneral(id: number, companyId: number): Promise> { + return await api.delete(`/v1/a76/units-of-measure/general/${id}/?company_id=${companyId}`); +} + +// --- Customs --- +export interface UnitOfMeasureCustoms { + id: number; + code: string; + description: string | null; + scaii_unit_code: string | null; + created_at: string | null; + updated_at: string | null; +} + +export interface UnitOfMeasureCustomsCreate { + code: string; + description?: string | null; + scaii_unit_code?: string | null; +} + +export interface UnitOfMeasureCustomsUpdate { + code?: string; + description?: string | null; + scaii_unit_code?: string | null; +} + +export interface UnitOfMeasureCustomsListResponse { + items: UnitOfMeasureCustoms[]; + total: number; + page: number; + page_size: number; + pages: number; +} + +export async function getUnitsOfMeasureCustoms( + page: number = 1, + pageSize: number = 50, + companyId: number, + filters: Record = {} +): Promise> { + const queryParams = new URLSearchParams({ + page: page.toString(), + page_size: pageSize.toString(), + company_id: companyId.toString(), + ...filters + }); + return await api.get(`/v1/a76/units-of-measure/customs/?${queryParams.toString()}`); +} + +export async function createUnitOfMeasureCustoms(data: UnitOfMeasureCustomsCreate, companyId: number): Promise> { + return await api.post(`/v1/a76/units-of-measure/customs/?company_id=${companyId}`, data); +} + +export async function updateUnitOfMeasureCustoms(id: number, data: UnitOfMeasureCustomsUpdate, companyId: number): Promise> { + return await api.put(`/v1/a76/units-of-measure/customs/${id}/?company_id=${companyId}`, data); +} + +export async function deleteUnitOfMeasureCustoms(id: number, companyId: number): Promise> { + return await api.delete(`/v1/a76/units-of-measure/customs/${id}/?company_id=${companyId}`); } diff --git a/frontend/src/lib/api/dashboard/a76/invoices.ts b/frontend/src/lib/api/dashboard/a76/invoices.ts new file mode 100644 index 00000000..461b8346 --- /dev/null +++ b/frontend/src/lib/api/dashboard/a76/invoices.ts @@ -0,0 +1,318 @@ +/** + * API Client para Facturas (Invoices) + * Gestiona las operaciones CRUD para facturas y sus relaciones + */ +import { api } from '$lib/api'; + +export type OperationType = 'imp' | 'exp'; +export type TransportType = 'none' | 'transport' | 'box' | 'licence plates' | 'truck' | 'vessel' | 'rail barge' | 'container' | 'airplane' | 'gondola' | 'flatbed'; + +// --- Interfaces --- + +export interface InvoiceComplianceMx { + invoice_id?: number; + pedimento?: string | null; + pedimento_code?: string | null; + remesa?: number | null; + aduana?: string | null; + provider_header?: string | null; + provider_id?: string | null; + sold_to_header?: string | null; + sold_to_id?: string | null; + shipped_to_header?: string | null; + shipped_to_id?: string | null; + shipped_by_header?: string | null; + shipped_by_id?: string | null; + customs_broker_id?: string | null; + is_mixed?: boolean | null; + waste_type?: string | null; + appendix_17?: number | null; + edocument?: string | null; + electronic_signature?: string | null; + sem_id?: number | null; +} + +export interface InvoiceFinancials { + id?: number; + invoice_id?: number; + currency?: string | null; + currency_type?: string | null; + exchange_rate?: number | null; + value_mn?: number | null; + value_me?: number | null; + customs_value_mn?: number | null; + freight?: number | null; + insurance?: number | null; + iva_mn?: number | null; + iva_factor?: number | null; + total_quantity?: number | null; + gross_weight?: number | null; + net_weight?: number | null; + bundle_count?: number | null; +} + +export interface InvoiceLogistics { + id?: number; + invoice_id?: number; + carrier_id?: string | null; + transport_type?: TransportType | null; + transport_mode?: string | null; + driver_name?: string | null; + is_rail?: string | null; + rail_id?: string | null; + vehicle_num?: string | null; + license_plate?: string | null; + seal_number?: string | null; + guide_number?: string | null; + entry_exit_date?: string | null; +} + +export interface InvoiceSalesDetails { + id?: number; + invoice_id?: number; + line_number: number; + sales_order?: string | null; + colors_description?: string | null; + square_color_code?: string | null; + line_bundles?: number | null; +} + +export interface InvoiceCollections { + id?: number; + invoice_id?: number; + concept?: string | null; + is_collected?: number | null; + collection_date?: string | null; + amount?: number | null; + collector_user?: string | null; +} + +export interface Invoice { + id: number; + tenant_id: number; + company_id: number; + operation_type?: OperationType | null; + invoice_type?: string | null; + invoice_number?: string | null; + project_number?: string | null; + purchase_order?: string | null; + related_doc_id?: number | null; + invoice_date?: string | null; + capture_date: string; + is_updated?: boolean | null; + updated_date?: string | null; + who_updated?: string | null; + traffic_light_status?: string | null; + process_log?: string | null; + observation_es?: string | null; + observation_en?: string | null; + comments_status?: string | null; + cfdi_uuid?: string | null; + path_pdf?: string | null; + path_xml?: string | null; + compliance_mx?: InvoiceComplianceMx | null; + financials?: InvoiceFinancials | null; + logistics?: InvoiceLogistics[]; + details?: InvoiceSalesDetails[]; + collections?: InvoiceCollections[]; +} + +export interface InvoiceListResponse { + items: Invoice[]; + total: number; + page: number; + page_size: number; +} + +export interface InvoiceData { + operation_type?: OperationType | null; + invoice_type?: string | null; + invoice_number?: string | null; + project_number?: string | null; + purchase_order?: string | null; + related_doc_id?: number | null; + invoice_date?: string | null; + traffic_light_status?: string | null; + process_log?: string | null; + observation_es?: string | null; + observation_en?: string | null; + comments_status?: string | null; + cfdi_uuid?: string | null; + path_pdf?: string | null; + path_xml?: string | null; + compliance_mx?: Omit | null; + financials?: Omit | null; + logistics?: Omit[] | null; + details?: Omit[] | null; + collections?: Omit[] | null; +} + +export interface UpdateInvoiceData { + operation_type?: OperationType | null; + invoice_type?: string | null; + invoice_number?: string | null; + project_number?: string | null; + purchase_order?: string | null; + related_doc_id?: number | null; + invoice_date?: string | null; + traffic_light_status?: string | null; + process_log?: string | null; + observation_es?: string | null; + observation_en?: string | null; + comments_status?: string | null; + cfdi_uuid?: string | null; + path_pdf?: string | null; + path_xml?: string | null; + compliance_mx?: Partial | null; + financials?: Partial | null; + logistics?: Partial[] | null; + details?: Partial[] | null; + collections?: Partial[] | null; +} + +/** + * API para Facturas + */ +export const invoicesApi = { + /** + * Lista todas las facturas con paginación + */ + list: (companyId: number, page = 1, pageSize = 50, filters?: Record) => { + const params = new URLSearchParams({ + company_id: companyId.toString(), + page: page.toString(), + page_size: pageSize.toString() + }); + + // Agregar filtros si existen + if (filters) { + Object.entries(filters).forEach(([key, value]) => { + if (value !== null && value !== undefined && value !== '') { + params.append(key, String(value)); + } + }); + } + + return api.get(`/v1/a76/invoices?${params.toString()}`); + }, + + /** + * Obtiene una factura por ID + */ + get: (invoiceId: number, companyId: number) => { + const params = new URLSearchParams({ + company_id: companyId.toString() + }); + return api.get(`/v1/a76/invoices/${invoiceId}?${params.toString()}`); + }, + + /** + * Crea una nueva factura + */ + create: (companyId: number, data: CreateInvoiceData) => { + const params = new URLSearchParams({ + company_id: companyId.toString() + }); + return api.post(`/v1/a76/invoices?${params.toString()}`, data); + }, + + /** + * Actualiza una factura existente + */ + update: (invoiceId: number, companyId: number, data: UpdateInvoiceData) => { + const params = new URLSearchParams({ + company_id: companyId.toString() + }); + return api.put(`/v1/a76/invoices/${invoiceId}?${params.toString()}`, data); + }, + + /** + * Elimina una factura + */ + delete: (invoiceId: number, companyId: number) => { + const params = new URLSearchParams({ + company_id: companyId.toString() + }); + return api.delete(`/v1/a76/invoices/${invoiceId}?${params.toString()}`); + }, + + // --- Nested Resources --- + + /** + * Logística de factura + */ + logistics: { + list: (invoiceId: number, companyId: number) => { + const params = new URLSearchParams({ + company_id: companyId.toString() + }); + return api.get(`/v1/a76/invoices/${invoiceId}/logistics?${params.toString()}`); + }, + + create: (invoiceId: number, companyId: number, data: Omit) => { + const params = new URLSearchParams({ + company_id: companyId.toString() + }); + return api.post(`/v1/a76/invoices/${invoiceId}/logistics?${params.toString()}`, data); + }, + + delete: (invoiceId: number, logisticsId: number, companyId: number) => { + const params = new URLSearchParams({ + company_id: companyId.toString() + }); + return api.delete(`/v1/a76/invoices/${invoiceId}/logistics/${logisticsId}?${params.toString()}`); + } + }, + + /** + * Detalles de venta de factura + */ + details: { + list: (invoiceId: number, companyId: number) => { + const params = new URLSearchParams({ + company_id: companyId.toString() + }); + return api.get(`/v1/a76/invoices/${invoiceId}/details?${params.toString()}`); + }, + + create: (invoiceId: number, companyId: number, data: Omit) => { + const params = new URLSearchParams({ + company_id: companyId.toString() + }); + return api.post(`/v1/a76/invoices/${invoiceId}/details?${params.toString()}`, data); + }, + + delete: (invoiceId: number, detailId: number, companyId: number) => { + const params = new URLSearchParams({ + company_id: companyId.toString() + }); + return api.delete(`/v1/a76/invoices/${invoiceId}/details/${detailId}?${params.toString()}`); + } + }, + + /** + * Cobranzas de factura + */ + collections: { + list: (invoiceId: number, companyId: number) => { + const params = new URLSearchParams({ + company_id: companyId.toString() + }); + return api.get(`/v1/a76/invoices/${invoiceId}/collections?${params.toString()}`); + }, + + create: (invoiceId: number, companyId: number, data: Omit) => { + const params = new URLSearchParams({ + company_id: companyId.toString() + }); + return api.post(`/v1/a76/invoices/${invoiceId}/collections?${params.toString()}`, data); + }, + + delete: (invoiceId: number, collectionId: number, companyId: number) => { + const params = new URLSearchParams({ + company_id: companyId.toString() + }); + return api.delete(`/v1/a76/invoices/${invoiceId}/collections/${collectionId}?${params.toString()}`); + } + } +}; diff --git a/frontend/src/lib/api/dashboard/refrence_data/invoice_types.ts b/frontend/src/lib/api/dashboard/refrence_data/invoice_types.ts index 8b132732..67bdae54 100644 --- a/frontend/src/lib/api/dashboard/refrence_data/invoice_types.ts +++ b/frontend/src/lib/api/dashboard/refrence_data/invoice_types.ts @@ -9,6 +9,7 @@ export interface InvoiceType { description: string; note?: string; type?: string; + operation?: string; } export interface InvoiceTypeListResponse { @@ -40,11 +41,20 @@ export const invoiceTypesApi = { * Lista todos los tipos de factura con paginación * @param page - Número de página (por defecto 1) * @param pageSize - Tamaño de página (por defecto 50) + * @param operation - Filtrar por tipo de operación (imp, exp) */ - list: (page = 1, pageSize = 50) => - api.get( - `/v1/public/refrence_data/invoice-types?page=${page}&page_size=${pageSize}` - ), + list: (page = 1, pageSize = 50, operation?: string) => { + const params = new URLSearchParams({ + page: page.toString(), + page_size: pageSize.toString() + }); + if (operation) { + params.append('operation', operation); + } + return api.get( + `/v1/public/refrence_data/invoice-types?${params.toString()}` + ); + }, /** * Obtiene un tipo de factura por key diff --git a/frontend/src/lib/assets/favicon.svg b/frontend/src/lib/assets/favicon.svg index cc5dc66a..1e26f0a3 100644 --- a/frontend/src/lib/assets/favicon.svg +++ b/frontend/src/lib/assets/favicon.svg @@ -1 +1,13 @@ -svelte-logo \ No newline at end of file + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/lib/components/dashboard/company/create-edit-dialog.svelte b/frontend/src/lib/components/dashboard/company/create-edit-dialog.svelte deleted file mode 100644 index 96ed1222..00000000 --- a/frontend/src/lib/components/dashboard/company/create-edit-dialog.svelte +++ /dev/null @@ -1,127 +0,0 @@ - - - - - - {title} - - -
{ e.preventDefault(); handleSubmit(); }} class="space-y-4"> - {#if error} -
{error}
- {/if} - -
- - -
- -
- - -
- -
- - -
- -
- - -
- - - - - -
-
-
diff --git a/frontend/src/lib/components/dashboard/customs_brokers/create-dialog.svelte b/frontend/src/lib/components/dashboard/customs_brokers/create-dialog.svelte index e623e2f2..fd76c8cf 100644 --- a/frontend/src/lib/components/dashboard/customs_brokers/create-dialog.svelte +++ b/frontend/src/lib/components/dashboard/customs_brokers/create-dialog.svelte @@ -1,381 +1,221 @@ - - - - Nuevo Agente Aduanal - - Completa los datos para crear un nuevo agente aduanal. - - + + + + + {mode === "create" ? "Nuevo Agente Aduanal" : "Editar Agente Aduanal"} + + + Ingresa los datos generales del agente. La configuración de VU y Personal se gestiona aparte. + + -
- {#if error} -
- {error} -
- {/if} +
+ +
+

Identificación

+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
- -
-

Información Básica

- -
-
- - -
+ -
- - -
-
+
+

Contacto

+
+
+ + +
+
+ + +
+
+
-
- - -
+ -
-
- - -
+
+

Dirección Fiscal

+ +
+ + +
-
- - -
-
-
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+
- -
-

Información de Contacto

- -
-
- - -
+
-
- - -
-
- -
- - -
- -
- - -
-
- - -
-

Dirección

- -
- - -
- -
-
- - -
- -
- - -
- -
- - -
-
- -
- - -
-
- - -
-

Información Fiscal

- -
-
- - -
- -
- - -
-
- -
- - -
-
- - - - - -
-
-
+ + + + +
+
\ No newline at end of file diff --git a/frontend/src/lib/components/dashboard/exchange-rate/columns.ts b/frontend/src/lib/components/dashboard/exchange-rate/columns.ts index 2b6b07b0..00dfe0db 100644 --- a/frontend/src/lib/components/dashboard/exchange-rate/columns.ts +++ b/frontend/src/lib/components/dashboard/exchange-rate/columns.ts @@ -5,44 +5,44 @@ import DataTableActions from './data-table-actions.svelte'; export function createColumns(onSuccess?: () => void): ColumnDef[] { return [ - { - accessorKey: 'date', - header: 'Fecha', - cell: ({ row }) => { - const dateStr = row.original.date; - if (!dateStr) return 'N/A'; - const date = new Date(dateStr); - return date.toLocaleDateString('es-MX'); + { + accessorKey: 'date', + header: 'Fecha', + cell: ({ row }) => { + const dateStr = row.original.date; + if (!dateStr) return 'N/A'; + const date = new Date(dateStr); + return date.toLocaleDateString('es-MX'); + } + }, + { + accessorKey: 'value', + header: 'Valor', + cell: ({ row }) => { + const value = row.original.value; + if (value === null || value === undefined) return 'N/A'; + return value.toFixed(6); + } + }, + { + accessorKey: 'local_currency', + header: 'Moneda Local', + cell: ({ row }) => row.original.local_currency ?? 'N/A' + }, + { + accessorKey: 'foreign_currency', + header: 'Moneda Extranjera', + cell: ({ row }) => row.original.foreign_currency ?? 'N/A' + }, + { + id: 'actions', + header: 'Acciones', + cell: ({ row }) => { + return renderComponent(DataTableActions, { + item: row.original, + onSuccess + }); + } } - }, - { - accessorKey: 'value', - header: 'Tipo de Cambio', - cell: ({ row }) => { - const value = row.original.value; - if (value === null || value === undefined) return 'N/A'; - return value.toFixed(6); - } - }, - { - accessorKey: 'local_currency', - header: 'Moneda Local', - cell: ({ row }) => row.original.local_currency ?? 'N/A' - }, - { - accessorKey: 'foreign_currency', - header: 'Moneda Extranjera', - cell: ({ row }) => row.original.foreign_currency ?? 'N/A' - }, - { - id: 'actions', - header: 'Acciones', - cell: ({ row }) => { - return renderComponent(DataTableActions, { - item: row.original, - onSuccess - }); - } - } ]; } \ No newline at end of file diff --git a/frontend/src/lib/components/dashboard/exchange-rate/create-edit-dialog.svelte b/frontend/src/lib/components/dashboard/exchange-rate/create-edit-dialog.svelte index 940e7f78..34c61657 100644 --- a/frontend/src/lib/components/dashboard/exchange-rate/create-edit-dialog.svelte +++ b/frontend/src/lib/components/dashboard/exchange-rate/create-edit-dialog.svelte @@ -1,164 +1,151 @@ - - - - {isEdit ? 'Editar' : 'Crear'} Tipo de Cambio - + + + + + + + {title} + -
-
- - -
+ { e.preventDefault(); handleSubmit(); }} class="space-y-4 py-4"> + {#if error} +
+ {error} +
+ {/if} -
- - -
+
+
+ +
+ +
+
-
- - -
+
+ +
+ +
+
-
- - -
+
+ +
+ +
+
- {#if error} -

{error}

- {/if} +
+ +
+ +
+
+
-
- - -
-
-
-
+ + + + + +
+ +
\ No newline at end of file diff --git a/frontend/src/lib/components/dashboard/exchange-rate/data-table-actions.svelte b/frontend/src/lib/components/dashboard/exchange-rate/data-table-actions.svelte index 56da5881..cce3e6b2 100644 --- a/frontend/src/lib/components/dashboard/exchange-rate/data-table-actions.svelte +++ b/frontend/src/lib/components/dashboard/exchange-rate/data-table-actions.svelte @@ -27,23 +27,21 @@ const companyId = companyStore.activeCompany?.id; if (!companyId) { - alert('No hay compañía seleccionada'); + alert('❌ Error: No hay compañía seleccionada'); return; } loading = true; - error = null; try { await deleteExchangeRate(item.id, companyId); - - // Éxito + alert(`✅ Tipo de cambio del ${new Date(item.date).toLocaleDateString('es-MX')} eliminado correctamente`); if (onSuccess) { onSuccess(); } } catch (err: any) { error = err.message || 'Error al eliminar el tipo de cambio'; - alert(`Error: ${error}`); + alert(`❌ Error: ${error}`); console.error('Error deleting:', err); } finally { loading = false; diff --git a/frontend/src/lib/components/dashboard/exchange-rate/data-table.svelte b/frontend/src/lib/components/dashboard/exchange-rate/data-table.svelte index ef98de23..72fb9bf3 100644 --- a/frontend/src/lib/components/dashboard/exchange-rate/data-table.svelte +++ b/frontend/src/lib/components/dashboard/exchange-rate/data-table.svelte @@ -1,123 +1,106 @@ -
-
- - - {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} - - {#each headerGroup.headers as header (header.id)} - - {#if !header.isPlaceholder} - - {/if} - - {/each} - - {/each} - - - {#each table.getRowModel().rows as row (row.id)} - - {#each row.getVisibleCells() as cell (cell.id)} - - - - {/each} - - {:else} - - - No hay resultados. - - - {/each} - - - {#if hasMore} - - -
- {#if loading} -
-
- Cargando más... -
- {:else} -
- Desplázate para cargar más -
- {/if} -
-
-
- {/if} -
-
-
+
+ + + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} + + {#each headerGroup.headers as header (header.id)} + + {#if !header.isPlaceholder} + + {/if} + + {/each} + + {/each} + + + {#each table.getRowModel().rows as row (row.id)} + + {#each row.getVisibleCells() as cell (cell.id)} + + + + {/each} + + {:else} + + + No hay resultados. + + + {/each} + + +
+ +
+
+ Total: {totalItems} registros +
+
+ + +
diff --git a/frontend/src/lib/components/dashboard/general_catalogs/classification/create-edit-dialog.svelte b/frontend/src/lib/components/dashboard/general_catalogs/classification/create-edit-dialog.svelte new file mode 100644 index 00000000..86612132 --- /dev/null +++ b/frontend/src/lib/components/dashboard/general_catalogs/classification/create-edit-dialog.svelte @@ -0,0 +1,115 @@ + + + + + + {title} + + +
{ e.preventDefault(); handleSubmit(); }} class="space-y-4"> + {#if error} +
+ {error} +
+ {/if} + +
+
+ + +
+
+ + + + + +
+
+
\ No newline at end of file diff --git a/frontend/src/lib/components/dashboard/general_catalogs/classification_concepts/columns.ts b/frontend/src/lib/components/dashboard/general_catalogs/classification_concepts/columns.ts new file mode 100644 index 00000000..6bc0b68f --- /dev/null +++ b/frontend/src/lib/components/dashboard/general_catalogs/classification_concepts/columns.ts @@ -0,0 +1,29 @@ +import type { ClassificationConcept } from '$lib/api/dashboard/a76/general_catalogs/classification-concepts'; +import type { ColumnDef } from '@tanstack/table-core'; +import { renderComponent } from '$lib/components/ui/data-table'; +import DataTableActions from './data-table-actions.svelte'; + +export function createColumns(onSuccess?: () => void): ColumnDef[] { + return [ + { + accessorKey: 'classification', + header: 'Clasificación', + cell: ({ row }) => row.original.classification || '-' + }, + { + accessorKey: 'description', + header: 'Descripción', + cell: ({ row }) => row.original.description || '-' + }, + { + id: 'actions', + header: 'Acciones', + cell: ({ row }) => { + return renderComponent(DataTableActions, { + item: row.original, + onSuccess + }); + } + } + ]; +} diff --git a/frontend/src/lib/components/dashboard/general_catalogs/classification_concepts/data-table-actions.svelte b/frontend/src/lib/components/dashboard/general_catalogs/classification_concepts/data-table-actions.svelte new file mode 100644 index 00000000..d13cf06c --- /dev/null +++ b/frontend/src/lib/components/dashboard/general_catalogs/classification_concepts/data-table-actions.svelte @@ -0,0 +1,89 @@ + + + + + {#snippet child({ props })} + + {/snippet} + + + Acciones + dialogOpen = true}> + + Editar + + + + {#if loading} + + {:else} + + {/if} + Eliminar + + + + + diff --git a/frontend/src/lib/components/dashboard/general_catalogs/classification_concepts/data-table.svelte b/frontend/src/lib/components/dashboard/general_catalogs/classification_concepts/data-table.svelte new file mode 100644 index 00000000..72fb9bf3 --- /dev/null +++ b/frontend/src/lib/components/dashboard/general_catalogs/classification_concepts/data-table.svelte @@ -0,0 +1,106 @@ + + +
+ + + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} + + {#each headerGroup.headers as header (header.id)} + + {#if !header.isPlaceholder} + + {/if} + + {/each} + + {/each} + + + {#each table.getRowModel().rows as row (row.id)} + + {#each row.getVisibleCells() as cell (cell.id)} + + + + {/each} + + {:else} + + + No hay resultados. + + + {/each} + + +
+ +
+
+ Total: {totalItems} registros +
+
+ + +
+
diff --git a/frontend/src/lib/components/dashboard/company/columns.ts b/frontend/src/lib/components/dashboard/general_catalogs/company/columns.ts similarity index 100% rename from frontend/src/lib/components/dashboard/company/columns.ts rename to frontend/src/lib/components/dashboard/general_catalogs/company/columns.ts diff --git a/frontend/src/lib/components/dashboard/general_catalogs/company/create-edit-dialog.svelte b/frontend/src/lib/components/dashboard/general_catalogs/company/create-edit-dialog.svelte new file mode 100644 index 00000000..2cc43f4a --- /dev/null +++ b/frontend/src/lib/components/dashboard/general_catalogs/company/create-edit-dialog.svelte @@ -0,0 +1,296 @@ + + + + + + {title} + + +
{ e.preventDefault(); handleSubmit(); }} class="space-y-4"> + {#if error} +
+ {error} +
+ {/if} + + + + General + Programas + Responsable + Config + + + +
+ + +
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+
+
+ + + + + +
+
+
\ No newline at end of file diff --git a/frontend/src/lib/components/dashboard/company/data-table-actions.svelte b/frontend/src/lib/components/dashboard/general_catalogs/company/data-table-actions.svelte similarity index 74% rename from frontend/src/lib/components/dashboard/company/data-table-actions.svelte rename to frontend/src/lib/components/dashboard/general_catalogs/company/data-table-actions.svelte index 2440e100..d180718b 100644 --- a/frontend/src/lib/components/dashboard/company/data-table-actions.svelte +++ b/frontend/src/lib/components/dashboard/general_catalogs/company/data-table-actions.svelte @@ -18,7 +18,7 @@ let dialogOpen = $state(false); async function handleDelete() { - if (!confirm(`¿Estás seguro de eliminar la empresa "${item.name}"?`)) { + if (!confirm(`¿Estás seguro de eliminar la empresa "${item.name}"?\n\nNota: No se puede eliminar si tiene registros relacionados.`)) { return; } @@ -28,16 +28,23 @@ try { const response = await deleteCompany(item.id); + // Si hay error en la respuesta if (response.error) { - alert(`Error al eliminar: ${response.error}`); + alert(`❌ Error al eliminar:\n\n${response.error}`); return; } - if (onSuccess) { - onSuccess(); + // Éxito (status 204 o 200) + if (response.status === 204 || response.status === 200 || !response.error) { + alert(`✅ Empresa "${item.name}" eliminada correctamente`); + + if (onSuccess) { + onSuccess(); + } } } catch (e) { - alert('Error al eliminar el registro'); + const errorMsg = e instanceof Error ? e.message : 'Error desconocido al eliminar el registro'; + alert(`❌ Error al eliminar:\n\n${errorMsg}`); } finally { loading = false; } @@ -55,7 +62,7 @@ Acciones - dialogOpen = true}> + dialogOpen = true}> Editar diff --git a/frontend/src/lib/components/dashboard/general_catalogs/company/data-table.svelte b/frontend/src/lib/components/dashboard/general_catalogs/company/data-table.svelte new file mode 100644 index 00000000..547154cb --- /dev/null +++ b/frontend/src/lib/components/dashboard/general_catalogs/company/data-table.svelte @@ -0,0 +1,109 @@ + + +
+ + + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} + + {#each headerGroup.headers as header (header.id)} + + {#if !header.isPlaceholder} + + {/if} + + {/each} + + {/each} + + + {#each table.getRowModel().rows as row (row.id)} + + {#each row.getVisibleCells() as cell (cell.id)} + + + + {/each} + + {:else} + + + No hay resultados. + + + {/each} + + +
+ +
+
+ Total: {totalItems} registros +
+
+ + +
+
\ No newline at end of file diff --git a/frontend/src/lib/components/dashboard/general_catalogs/concepts/columns.ts b/frontend/src/lib/components/dashboard/general_catalogs/concepts/columns.ts new file mode 100644 index 00000000..a466a539 --- /dev/null +++ b/frontend/src/lib/components/dashboard/general_catalogs/concepts/columns.ts @@ -0,0 +1,39 @@ +import type { Concept } from '$lib/api/dashboard/a76/general_catalogs/concepts'; +import type { ColumnDef } from '@tanstack/table-core'; +import { renderComponent } from '$lib/components/ui/data-table'; +import DataTableActions from './data-table-actions.svelte'; + +export function createColumns(onSuccess?: () => void): ColumnDef[] { + return [ + { + accessorKey: 'code', + header: 'Código', + cell: ({ row }) => row.original.code || '-' + }, + { + accessorKey: 'description', + header: 'Descripción', + cell: ({ row }) => row.original.description || '-' + }, + { + accessorKey: 'type', + header: 'Tipo', + cell: ({ row }) => row.original.type || '-' + }, + { + accessorKey: 'section', + header: 'Sección', + cell: ({ row }) => row.original.section?.toString() || '-' + }, + { + id: 'actions', + header: 'Acciones', + cell: ({ row }) => { + return renderComponent(DataTableActions, { + item: row.original, + onSuccess + }); + } + } + ]; +} diff --git a/frontend/src/lib/components/dashboard/general_catalogs/concepts/create-edit-dialog.svelte b/frontend/src/lib/components/dashboard/general_catalogs/concepts/create-edit-dialog.svelte new file mode 100644 index 00000000..779795b6 --- /dev/null +++ b/frontend/src/lib/components/dashboard/general_catalogs/concepts/create-edit-dialog.svelte @@ -0,0 +1,213 @@ + + + + + + {title} + + +
{ e.preventDefault(); handleSubmit(); }} class="space-y-4"> + {#if error} +
+ {error} +
+ {/if} + + + +
+ + +
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+
+ + + + + +
+
+
\ No newline at end of file diff --git a/frontend/src/lib/components/dashboard/general_catalogs/concepts/data-table-actions.svelte b/frontend/src/lib/components/dashboard/general_catalogs/concepts/data-table-actions.svelte new file mode 100644 index 00000000..da527c98 --- /dev/null +++ b/frontend/src/lib/components/dashboard/general_catalogs/concepts/data-table-actions.svelte @@ -0,0 +1,89 @@ + + + + + {#snippet child({ props })} + + {/snippet} + + + Acciones + dialogOpen = true}> + + Editar + + + + {#if loading} + + {:else} + + {/if} + Eliminar + + + + + diff --git a/frontend/src/lib/components/dashboard/general_catalogs/concepts/data-table.svelte b/frontend/src/lib/components/dashboard/general_catalogs/concepts/data-table.svelte new file mode 100644 index 00000000..72fb9bf3 --- /dev/null +++ b/frontend/src/lib/components/dashboard/general_catalogs/concepts/data-table.svelte @@ -0,0 +1,106 @@ + + +
+ + + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} + + {#each headerGroup.headers as header (header.id)} + + {#if !header.isPlaceholder} + + {/if} + + {/each} + + {/each} + + + {#each table.getRowModel().rows as row (row.id)} + + {#each row.getVisibleCells() as cell (cell.id)} + + + + {/each} + + {:else} + + + No hay resultados. + + + {/each} + + +
+ +
+
+ Total: {totalItems} registros +
+
+ + +
+
diff --git a/frontend/src/lib/components/dashboard/general_catalogs/customs_broker_concepts/columns.ts b/frontend/src/lib/components/dashboard/general_catalogs/customs_broker_concepts/columns.ts new file mode 100644 index 00000000..09fd8bf8 --- /dev/null +++ b/frontend/src/lib/components/dashboard/general_catalogs/customs_broker_concepts/columns.ts @@ -0,0 +1,39 @@ +import type { CustomsBrokerConcept } from '$lib/api/dashboard/a76/general_catalogs/customs-broker-concepts'; +import type { ColumnDef } from '@tanstack/table-core'; +import { renderComponent } from '$lib/components/ui/data-table'; +import DataTableActions from './data-table-actions.svelte'; + +export function createColumns(onSuccess?: () => void): ColumnDef[] { + return [ + { + accessorKey: 'code', + header: 'Código', + cell: ({ row }) => row.original.code || '-' + }, + { + accessorKey: 'description', + header: 'Descripción', + cell: ({ row }) => row.original.description || '-' + }, + { + accessorKey: 'type', + header: 'Tipo', + cell: ({ row }) => row.original.type || '-' + }, + { + accessorKey: 'section', + header: 'Sección', + cell: ({ row }) => row.original.section?.toString() || '-' + }, + { + id: 'actions', + header: 'Acciones', + cell: ({ row }) => { + return renderComponent(DataTableActions, { + item: row.original, + onSuccess + }); + } + } + ]; +} diff --git a/frontend/src/lib/components/dashboard/general_catalogs/customs_broker_concepts/create-edite-dialoge.svelte b/frontend/src/lib/components/dashboard/general_catalogs/customs_broker_concepts/create-edite-dialoge.svelte new file mode 100644 index 00000000..a6ba9c4b --- /dev/null +++ b/frontend/src/lib/components/dashboard/general_catalogs/customs_broker_concepts/create-edite-dialoge.svelte @@ -0,0 +1,165 @@ + + + + + + {title} + + {isEdit ? 'Modifica el concepto del agente aduanal' : 'Crea un nuevo concepto'} + + + +
{ e.preventDefault(); handleSubmit(); }} class="space-y-4"> + {#if error} +
+ {error} +
+ {/if} + +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+
+
\ No newline at end of file diff --git a/frontend/src/lib/components/dashboard/general_catalogs/customs_broker_concepts/data-table-actions.svelte b/frontend/src/lib/components/dashboard/general_catalogs/customs_broker_concepts/data-table-actions.svelte new file mode 100644 index 00000000..66dde523 --- /dev/null +++ b/frontend/src/lib/components/dashboard/general_catalogs/customs_broker_concepts/data-table-actions.svelte @@ -0,0 +1,89 @@ + + + + + {#snippet child({ props })} + + {/snippet} + + + Acciones + dialogOpen = true}> + + Editar + + + + {#if loading} + + {:else} + + {/if} + Eliminar + + + + + diff --git a/frontend/src/lib/components/dashboard/general_catalogs/customs_broker_concepts/data-table.svelte b/frontend/src/lib/components/dashboard/general_catalogs/customs_broker_concepts/data-table.svelte new file mode 100644 index 00000000..72fb9bf3 --- /dev/null +++ b/frontend/src/lib/components/dashboard/general_catalogs/customs_broker_concepts/data-table.svelte @@ -0,0 +1,106 @@ + + +
+ + + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} + + {#each headerGroup.headers as header (header.id)} + + {#if !header.isPlaceholder} + + {/if} + + {/each} + + {/each} + + + {#each table.getRowModel().rows as row (row.id)} + + {#each row.getVisibleCells() as cell (cell.id)} + + + + {/each} + + {:else} + + + No hay resultados. + + + {/each} + + +
+ +
+
+ Total: {totalItems} registros +
+
+ + +
+
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 new file mode 100644 index 00000000..47bd0a8e --- /dev/null +++ b/frontend/src/lib/components/dashboard/general_catalogs/doda/create-edit-dialog.svelte @@ -0,0 +1,361 @@ + + + + + + {title} + + +
{ e.preventDefault(); handleSubmit(); }} class="py-4"> + {#if error} +
+ {error} +
+ {/if} + + + + General + Aduana/Transp. + SAT / Digital + Otros + + + + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+ + + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+ + + +
+
+ + +
+
+ + +
+
+
+ + +
+
+ +