diff --git a/backend/alembic/versions/add_timestamps_to_classes.py b/backend/alembic/versions/add_timestamps_to_classes.py deleted file mode 100644 index a2b4f554..00000000 --- a/backend/alembic/versions/add_timestamps_to_classes.py +++ /dev/null @@ -1,67 +0,0 @@ -"""Add timestamp fields to classes table - -Revision ID: add_timestamps_classes -Revises: 7937209f9718 -Create Date: 2025-11-16 00:20:00.000000 - -""" -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision: str = 'add_timestamps_classes' -down_revision: Union[str, None] = '7937209f9718' -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # Add created_at column with default value - op.execute(""" - ALTER TABLE a76.classes - ADD COLUMN created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL - """) - - # Add updated_at column with default value - op.execute(""" - ALTER TABLE a76.classes - ADD COLUMN updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL - """) - - # Add deleted_at column (nullable for soft deletes) - op.execute(""" - ALTER TABLE a76.classes - ADD COLUMN deleted_at TIMESTAMP WITH TIME ZONE - """) - - # Create trigger to auto-update updated_at - op.execute(""" - CREATE OR REPLACE FUNCTION a76.update_classes_updated_at() - RETURNS TRIGGER AS $$ - BEGIN - NEW.updated_at = CURRENT_TIMESTAMP; - RETURN NEW; - END; - $$ language 'plpgsql'; - """) - - op.execute(""" - CREATE TRIGGER update_classes_updated_at - BEFORE UPDATE ON a76.classes - FOR EACH ROW - EXECUTE FUNCTION a76.update_classes_updated_at(); - """) - - -def downgrade() -> None: - # Drop trigger and function - op.execute("DROP TRIGGER IF EXISTS update_classes_updated_at ON a76.classes") - op.execute("DROP FUNCTION IF EXISTS a76.update_classes_updated_at()") - - # Drop columns - op.execute("ALTER TABLE a76.classes DROP COLUMN IF EXISTS deleted_at") - op.execute("ALTER TABLE a76.classes DROP COLUMN IF EXISTS updated_at") - op.execute("ALTER TABLE a76.classes DROP COLUMN IF EXISTS created_at") diff --git a/backend/api/v1/modules/a76/clients_and_providers/dto.py b/backend/api/v1/modules/a76/clients_and_providers/dto.py index 10fa8858..f77df843 100644 --- a/backend/api/v1/modules/a76/clients_and_providers/dto.py +++ b/backend/api/v1/modules/a76/clients_and_providers/dto.py @@ -4,7 +4,7 @@ Reemplaza schemas.py siguiendo enfoque DDD y estilo NestJS """ from decimal import Decimal -from typing import Literal, Optional +from typing import List, Literal, Optional from pydantic import BaseModel, Field @@ -119,7 +119,7 @@ class ClientProviderCreateDTO(BaseModel): is_national_provider: Optional[str] = Field( None, max_length=2, description="Is national provider" ) - enabled_disabled: Optional[int] = Field(None, description="Enabled/Disabled status") + is_active: Optional[bool] = Field(None, description="Enabled/Disabled status") # Nested DTOs address: Optional[ClientProviderAddressDTO] = Field( @@ -162,7 +162,7 @@ class ClientProviderUpdateDTO(BaseModel): is_national_provider: Optional[str] = Field( None, max_length=2, description="Is national provider" ) - enabled_disabled: Optional[int] = Field(None, description="Enabled/Disabled status") + is_active: Optional[bool] = Field(None, description="Enabled/Disabled status") # Nested DTOs address: Optional[ClientProviderAddressDTO] = Field( @@ -194,7 +194,7 @@ class ClientProviderResponseDTO(BaseModel): position: Optional[str] = None incoterm: Optional[str] = None is_national_provider: Optional[str] = None - enabled_disabled: Optional[int] = None + is_active: Optional[bool] = None tenant_id: int company_id: int @@ -215,7 +215,7 @@ class ClientProviderBasicDTO(BaseModel): short_name: Optional[str] = None rfc: Optional[str] = None client_or_provider: Optional[str] = None - enabled_disabled: Optional[int] = None + is_active: Optional[bool] = None class Config: from_attributes = True @@ -231,3 +231,15 @@ class ClientProviderListDTO(BaseModel): class Config: from_attributes = True + + +class ClientProviderPaginatedResponseDTO(BaseModel): + """DTO para respuesta paginada de clientes/proveedores""" + + items: List[ClientProviderResponseDTO] + total: int + page: int + page_size: int + + class Config: + from_attributes = True diff --git a/backend/api/v1/modules/a76/clients_and_providers/models.py b/backend/api/v1/modules/a76/clients_and_providers/models.py index da2a02eb..ea9a7f7d 100644 --- a/backend/api/v1/modules/a76/clients_and_providers/models.py +++ b/backend/api/v1/modules/a76/clients_and_providers/models.py @@ -15,7 +15,8 @@ from sqlalchemy import ( PrimaryKeyConstraint, SmallInteger, String, - Enum as PgEnum + Enum as PgEnum, + Boolean ) from sqlalchemy.orm import Mapped, mapped_column, relationship from enum import Enum @@ -60,8 +61,8 @@ class ClientProvider(Base, TenantScopedMixin): responsible: Mapped[Optional[str]] = mapped_column(String(80)) position: Mapped[Optional[str]] = mapped_column(String(30)) incoterm: Mapped[Optional[str]] = mapped_column(String(19)) - is_national_provider: Mapped[Optional[str]] = mapped_column(String(2)) - enabled_disabled: Mapped[Optional[int]] = mapped_column(SmallInteger) + is_national_provider: Mapped[Optional[bool]] = mapped_column(Boolean) + is_active: Mapped[Optional[bool]] = mapped_column(Boolean) # Relationships address: Mapped[Optional["ClientProviderAddress"]] = relationship( diff --git a/backend/api/v1/modules/a76/clients_and_providers/routes.py b/backend/api/v1/modules/a76/clients_and_providers/routes.py index 68e74d13..970b71ad 100644 --- a/backend/api/v1/modules/a76/clients_and_providers/routes.py +++ b/backend/api/v1/modules/a76/clients_and_providers/routes.py @@ -9,136 +9,57 @@ from core.security import get_current_user, validate_access_to_resource from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.orm import Session from api.v1.common.tenant_crud_routes import TenantCRUDRoutes +from .models import ClientOrProviderEnum from .dto import ( ClientProviderBasicDTO, ClientProviderCreateDTO, ClientProviderResponseDTO, ClientProviderUpdateDTO, + ClientProviderPaginatedResponseDTO, ) from .service import ClientProviderService from .models import ClientProvider -# Create base CRUD router using TenantCRUDRoutes factory -base_router = TenantCRUDRoutes( - service=ClientProviderService, - create_schema=ClientProviderCreateDTO, - update_schema=ClientProviderUpdateDTO, - response_schema=ClientProviderResponseDTO, - prefix="", - tags=[], - resource_name="Client/Provider", - id_name="id", # Using numeric ID - enable_list=True, # Enable GET /clients-providers with pagination - enable_filters=True, # Enable filtering - default_page_size=50, - max_page_size=100, -).router - # Create main router to add custom endpoints router = APIRouter(prefix="/clients-providers") -# Include base CRUD routes -router.include_router(base_router, prefix="") - -# Custom endpoints -@router.get("/clients", response_model=List[ClientProviderBasicDTO]) -async def get_clients_only( +@router.get("/", response_model=ClientProviderPaginatedResponseDTO) +async def get_clients_and_providers( company_id: int = Query(..., description="Company ID"), + type: Optional[ClientOrProviderEnum] = Query( + None, description="Type of entity (client or provider)" + ), + active: Optional[bool] = Query(None, description="Active status"), skip: int = Query(0, ge=0), limit: int = Query(100, ge=1, le=1000), db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user), ): - """Get only clients (client_or_provider = 'client')""" + """Get clients and providers""" tenant_id = validate_access_to_resource(db, company_id, current_user) - - clients = ( - db.query(ClientProvider) - .filter( - ClientProvider.tenant_id == tenant_id, - ClientProvider.company_id == company_id, - ClientProvider.client_or_provider == "client" - ) - .offset(skip) - .limit(limit) - .all() + + query = db.query(ClientProvider).filter( + ClientProvider.tenant_id == tenant_id, + ClientProvider.company_id == company_id, ) - return [ClientProviderBasicDTO.model_validate(c) for c in clients] + if type is not None: + query = query.filter(ClientProvider.client_or_provider == type) -@router.get("/providers", response_model=List[ClientProviderBasicDTO]) -async def get_providers_only( - company_id: int = Query(..., description="Company ID"), - skip: int = Query(0, ge=0), - limit: int = Query(100, ge=1, le=1000), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """Get only providers (client_or_provider = 'provider')""" - tenant_id = validate_access_to_resource(db, company_id, current_user) - - providers = ( - db.query(ClientProvider) - .filter( - ClientProvider.tenant_id == tenant_id, - ClientProvider.company_id == company_id, - ClientProvider.client_or_provider == "provider" - ) - .offset(skip) - .limit(limit) - .all() - ) - return [ClientProviderBasicDTO.model_validate(p) for p in providers] + if active is not None: + query = query.filter(ClientProvider.is_active == active) + total = query.count() + clients = query.offset(skip).limit(limit).all() -@router.get("/search/rfc/{rfc}", response_model=List[ClientProviderBasicDTO]) -async def search_by_rfc( - rfc: str, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """Search clients/providers by RFC""" - tenant_id = validate_access_to_resource(db, company_id, current_user) - - clients = ( - db.query(ClientProvider) - .filter( - ClientProvider.tenant_id == tenant_id, - ClientProvider.company_id == company_id, - ClientProvider.rfc.ilike(f"%{rfc}%") - ) - .all() - ) - return [ClientProviderBasicDTO.model_validate(c) for c in clients] - - -@router.patch("/{client_id}/toggle-status", response_model=ClientProviderResponseDTO) -async def toggle_clients_and_providers_status( - client_id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """Toggle client/provider enabled/disabled status""" - tenant_id = validate_access_to_resource(db, company_id, current_user) - - client = ClientProviderService.get_by_id(db, client_id, tenant_id, company_id) - if not client: - raise HTTPException(status_code=404, detail="Client/Provider not found") - - # Toggle status (1 = enabled, 0 = disabled) - client.enabled_disabled = 1 if client.enabled_disabled == 0 else 0 - - try: - db.commit() - db.refresh(client) - return client - except Exception as e: - db.rollback() - raise HTTPException(status_code=500, detail="Error updating status") + return { + "items": [ClientProviderResponseDTO.model_validate(c) for c in clients], + "total": total, + "page": (skip // limit) + 1, + "page_size": limit, + } @router.get("/{client_id}/basic", response_model=ClientProviderBasicDTO) @@ -150,9 +71,59 @@ async def get_clients_and_providers_basic_info( ): """Get basic information for a client/provider (without address and programs)""" tenant_id = validate_access_to_resource(db, company_id, current_user) - + client = ClientProviderService.get_by_id(db, client_id, tenant_id, company_id) if not client: raise HTTPException(status_code=404, detail="Client/Provider not found") - + return ClientProviderBasicDTO.model_validate(client) + + +@router.post("/", response_model=ClientProviderResponseDTO) +async def create_client_provider( + client_data: ClientProviderCreateDTO, + company_id: int = Query(..., description="Company ID"), + db: Session = Depends(get_core_db), + current_user: dict = Depends(get_current_user), +): + """Create a new client/provider""" + tenant_id = validate_access_to_resource(db, company_id, current_user) + + return ClientProviderService.create(db, client_data, tenant_id, company_id) + + +@router.patch("/{client_id}", response_model=ClientProviderResponseDTO) +async def update_client_provider( + client_id: int, + client_data: ClientProviderUpdateDTO, + company_id: int = Query(..., description="Company ID"), + db: Session = Depends(get_core_db), + current_user: dict = Depends(get_current_user), +): + """Update a client/provider""" + tenant_id = validate_access_to_resource(db, company_id, current_user) + + client = ClientProviderService.update( + db, client_id, tenant_id, company_id, client_data + ) + if not client: + raise HTTPException(status_code=404, detail="Client/Provider not found") + + return client + + +@router.delete("/{client_id}", response_model=bool) +async def delete_client_provider( + client_id: int, + company_id: int = Query(..., description="Company ID"), + db: Session = Depends(get_core_db), + current_user: dict = Depends(get_current_user), +): + """Delete a client/provider""" + tenant_id = validate_access_to_resource(db, company_id, current_user) + + success = ClientProviderService.delete(db, client_id, tenant_id, company_id) + if not success: + raise HTTPException(status_code=404, detail="Client/Provider not found") + + return success diff --git a/backend/api/v1/modules/a76/clients_and_providers/service.py b/backend/api/v1/modules/a76/clients_and_providers/service.py index c9bc22e3..5a329673 100644 --- a/backend/api/v1/modules/a76/clients_and_providers/service.py +++ b/backend/api/v1/modules/a76/clients_and_providers/service.py @@ -61,13 +61,17 @@ class ClientProviderService: ) if filters.get("status"): enabled = 1 if filters["status"] == "enabled" else 0 - query = query.filter(ClientProvider.enabled_disabled == enabled) + query = query.filter(ClientProvider.is_active == enabled) total = query.count() - clients = query.options( - joinedload(ClientProvider.address), - joinedload(ClientProvider.programs) - ).offset(skip).limit(limit).all() + clients = ( + query.options( + joinedload(ClientProvider.address), joinedload(ClientProvider.programs) + ) + .offset(skip) + .limit(limit) + .all() + ) return clients, total @@ -79,8 +83,7 @@ class ClientProviderService: return ( db.query(ClientProvider) .options( - joinedload(ClientProvider.address), - joinedload(ClientProvider.programs) + joinedload(ClientProvider.address), joinedload(ClientProvider.programs) ) .filter( ClientProvider.id == client_id, @@ -102,9 +105,7 @@ class ClientProviderService: # Create main client/provider data_dict = client_data.model_dump(exclude={"address", "programs"}) db_client = ClientProvider( - **data_dict, - tenant_id=tenant_id, - company_id=company_id + **data_dict, tenant_id=tenant_id, company_id=company_id ) db.add(db_client) @@ -112,8 +113,8 @@ class ClientProviderService: # Create address if provided if client_data.address: - db_address = ClientProviderAddress( - tenant_id=tenant_id, + db_address = ClientProviderAddress( + tenant_id=tenant_id, company_id=company_id, client_id=db_client.id, **client_data.address.model_dump(exclude_unset=True), @@ -122,7 +123,7 @@ class ClientProviderService: # Create programs if provided if client_data.programs: - db_programs = ClientProviderPrograms( + db_programs = ClientProviderPrograms( tenant_id=tenant_id, company_id=company_id, client_id=db_client.id, @@ -210,9 +211,7 @@ class ClientProviderService: ) @staticmethod - def delete( - db: Session, client_id: int, tenant_id: int, company_id: int - ) -> bool: + def delete(db: Session, client_id: int, tenant_id: int, company_id: int) -> bool: """Delete a client/provider""" client = ClientProviderService.get_by_id(db, client_id, tenant_id, company_id) if not client: @@ -351,7 +350,7 @@ class ClientProviderService: ) if enabled_only: - query = query.filter(ClientProvider.enabled_disabled == 1) + query = query.filter(ClientProvider.is_active == 1) # Contar total total = query.count() @@ -478,59 +477,4 @@ class ClientProviderService: logger.error(f"Error deleting client/provider {client_id}: {str(e)}") raise HTTPException( status_code=500, detail="Error deleting client/provider" - ) - - def get_clients_only( - self, skip: int = 0, limit: int = 100 - ) -> List[ClientProviderBasicDTO]: - """Obtiene solo clientes (client)""" - query = self.db.query(ClientProvider).filter( - ClientProvider.client_or_provider == "client" - ) - clients = query.offset(skip).limit(limit).all() - return [ClientProviderBasicDTO.model_validate(client) for client in clients] - - def get_providers_only( - self, skip: int = 0, limit: int = 100 - ) -> List[ClientProviderBasicDTO]: - """Obtiene solo proveedores (provider)""" - query = self.db.query(ClientProvider).filter( - ClientProvider.client_or_provider == "provider" - ) - providers = query.offset(skip).limit(limit).all() - return [ - ClientProviderBasicDTO.model_validate(provider) for provider in providers - ] - - def search_by_rfc(self, rfc: str) -> List[ClientProviderBasicDTO]: - """Busca clientes/proveedores por RFC""" - clients = ( - self.db.query(ClientProvider) - .filter(ClientProvider.rfc.ilike(f"%{rfc}%")) - .all() - ) - return [ClientProviderBasicDTO.model_validate(client) for client in clients] - - def toggle_status(self, client_id: str) -> Optional[ClientProviderResponseDTO]: - """Cambia el estado habilitado/deshabilitado""" - client = ( - self.db.query(ClientProvider) - .filter(ClientProvider.client_id == client_id) - .first() - ) - if not client: - return None - - # Toggle status (1 = habilitado, 0 = deshabilitado) - client.enabled_disabled = 1 if client.enabled_disabled == 0 else 0 - - try: - self.db.commit() - logger.info( - f"Client/Provider status toggled: {client_id} -> {client.enabled_disabled}" - ) - return self._get_client_with_relations(client_id) - except Exception as e: - self.db.rollback() - logger.error(f"Error toggling status for {client_id}: {str(e)}") - raise HTTPException(status_code=500, detail="Error updating status") + ) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/parts/dto.py b/backend/api/v1/modules/a76/parts/dto.py index 1414fd82..e21e7772 100644 --- a/backend/api/v1/modules/a76/parts/dto.py +++ b/backend/api/v1/modules/a76/parts/dto.py @@ -67,7 +67,7 @@ class PartCreateDTO(BaseModel): added_value: Optional[Decimal] = Field(None, description="Added value") # Status and media - enabled_disabled: Optional[int] = Field(None, description="Enabled/Disabled status") + is_active: Optional[bool] = Field(None, description="Enabled/Disabled status") creation_date: Optional[int] = Field(None, description="Creation date") part_photo: Optional[str] = Field( None, max_length=255, description="Part photo URL" @@ -132,7 +132,7 @@ class PartUpdateDTO(BaseModel): added_value: Optional[Decimal] = Field(None, description="Added value") # Status and media - enabled_disabled: Optional[int] = Field(None, description="Enabled/Disabled status") + is_active: Optional[bool] = Field(None, description="Enabled/Disabled status") part_photo: Optional[str] = Field( None, max_length=255, description="Part photo URL" ) @@ -178,7 +178,7 @@ class PartResponseDTO(BaseModel): added_value: Optional[Decimal] = None # Status and dates - enabled_disabled: Optional[int] = None + is_active: Optional[bool] = None creation_date: Optional[int] = None modification_date: Optional[int] = None modification_date_iso: Optional[datetime] = None @@ -200,7 +200,7 @@ class PartBasicDTO(BaseModel): part_class: Optional[str] = None unit_cost: Optional[Decimal] = None currency_key: Optional[str] = None - enabled_disabled: Optional[int] = None + is_active: Optional[bool] = None class Config: from_attributes = True diff --git a/backend/api/v1/modules/a76/parts/models.py b/backend/api/v1/modules/a76/parts/models.py index ef8b325b..b961c272 100644 --- a/backend/api/v1/modules/a76/parts/models.py +++ b/backend/api/v1/modules/a76/parts/models.py @@ -16,6 +16,7 @@ from sqlalchemy import ( SmallInteger, String, UniqueConstraint, + Boolean, ) from sqlalchemy.orm import Mapped, mapped_column, relationship @@ -90,7 +91,7 @@ class Part(Base, TenantScopedMixin): added_value: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # Status and dates - enabled_disabled: Mapped[Optional[int]] = mapped_column(SmallInteger) + is_active: Mapped[Optional[bool]] = mapped_column(Boolean) creation_date: Mapped[Optional[int]] = mapped_column() # FECHACREACIONPARTE modification_date: Mapped[Optional[int]] = mapped_column() # FECHAMODIFICA modification_date_iso: Mapped[Optional[datetime]] = ( diff --git a/backend/api/v1/modules/a76/parts/routes.py b/backend/api/v1/modules/a76/parts/routes.py index e271cb12..0f5996b6 100644 --- a/backend/api/v1/modules/a76/parts/routes.py +++ b/backend/api/v1/modules/a76/parts/routes.py @@ -348,7 +348,7 @@ async def get_part_basic_info( part_class=part.part_class, unit_cost=part.unit_cost, currency_key=part.currency_key, - enabled_disabled=part.enabled_disabled, + is_active=part.is_active, ) diff --git a/backend/api/v1/modules/a76/parts/service.py b/backend/api/v1/modules/a76/parts/service.py index d8ba3d06..48c2eab9 100644 --- a/backend/api/v1/modules/a76/parts/service.py +++ b/backend/api/v1/modules/a76/parts/service.py @@ -221,7 +221,7 @@ class PartService: return None # Toggle status (assuming 1 = enabled, 0 = disabled) - db_part.enabled_disabled = 1 if db_part.enabled_disabled == 0 else 0 + db_part.is_active = 1 if db_part.is_active == 0 else 0 db.commit() db.refresh(db_part) @@ -257,8 +257,8 @@ class PartService: ) # Partes habilitadas vs deshabilitadas - enabled_parts = db.query(Part).filter(Part.enabled_disabled == 1).count() - disabled_parts = db.query(Part).filter(Part.enabled_disabled == 0).count() + enabled_parts = db.query(Part).filter(Part.is_active == 1).count() + disabled_parts = db.query(Part).filter(Part.is_active == 0).count() return { "total_parts": total_parts, diff --git a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_dates.py b/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_dates.py index bd69786e..101b58b0 100644 --- a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_dates.py +++ b/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_dates.py @@ -7,9 +7,8 @@ from pydantic import BaseModel, ConfigDict, Field class PedimentoDatesBase(BaseModel): """Base schema for Pedimento Dates""" - entry_date: Optional[datetime] = Field(None, description="Entry date") - pedimento_date: Optional[datetime] = Field(None, description="Pedimento date") - payment_date: Optional[datetime] = Field(None, description="Payment date") + entry_date: Optional[datetime] = Field(None, description="Entry date") + payment_date: datetime = Field(None, description="Payment date") rectification_payment_date: Optional[datetime] = Field( None, description="Rectification payment date" ) @@ -19,21 +18,26 @@ class PedimentoDatesBase(BaseModel): original_date: Optional[datetime] = Field(None, description="Original date") start_date: Optional[datetime] = Field(None, description="Start date") end_date: Optional[datetime] = Field(None, description="End date") - capture_date: Optional[datetime] = Field(None, description="Capture date") - capture_time: Optional[time] = Field(None, description="Capture time") -class PedimentoDatesCreate(PedimentoDatesBase): - """Schema for creating a new Pedimento Dates""" +class PedimentoDatesCreate(BaseModel): + """Schema for creating a new Pedimento Dates - pedimento_id and tenant_id are set by backend""" - pass + entry_date: Optional[datetime] = Field(None, description="Entry date") + payment_date: datetime = Field(..., description="Payment date") + rectification_payment_date: Optional[datetime] = Field(None, description="Rectification payment date") + extraction_date: Optional[datetime] = Field(None, description="Extraction date") + submission_date: Optional[datetime] = Field(None, description="Submission date") + eucan_date: Optional[datetime] = Field(None, description="EUCAN date") + original_date: Optional[datetime] = Field(None, description="Original date") + start_date: Optional[datetime] = Field(None, description="Start date") + end_date: Optional[datetime] = Field(None, description="End date") class PedimentoDatesUpdate(BaseModel): """Schema for updating a Pedimento Dates""" - entry_date: Optional[datetime] = None - pedimento_date: Optional[datetime] = None + entry_date: Optional[datetime] = None payment_date: Optional[datetime] = None rectification_payment_date: Optional[datetime] = None extraction_date: Optional[datetime] = None @@ -42,8 +46,6 @@ class PedimentoDatesUpdate(BaseModel): original_date: Optional[datetime] = None start_date: Optional[datetime] = None end_date: Optional[datetime] = None - capture_date: Optional[datetime] = None - capture_time: Optional[time] = None class PedimentoDatesResponse(PedimentoDatesBase): diff --git a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_payments.py b/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_payments.py index eeaf384c..bdd98381 100644 --- a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_payments.py +++ b/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_payments.py @@ -25,14 +25,23 @@ class PedimentoPaymentsBase(BaseModel): total_cash_paid: Optional[int] = Field(None, description="Total cash paid") total_contributions: Optional[int] = Field(None, description="Total contributions") counter_payment: Optional[int] = Field(None, description="Counter payment") - pece_code: Optional[str] = Field(None, max_length=5, description="PECE code") - payment_id: Optional[int] = Field(None, description="Payment ID") + pece_code: Optional[str] = Field(None, max_length=5, description="PECE code") -class PedimentoPaymentsCreate(PedimentoPaymentsBase): - """Schema for creating a new Pedimento Payments""" +class PedimentoPaymentsCreate(BaseModel): + """Schema for creating a new Pedimento Payments - pedimento_id and tenant_id are set by backend""" - pass + acknowledgment: Optional[str] = Field(None, max_length=20, description="Acknowledgment") + operation_number: Optional[str] = Field(None, max_length=14, description="Operation number") + bank_code: Optional[int] = Field(None, description="Bank code") + cashier: Optional[str] = Field(None, max_length=2, description="Cashier") + date: Optional[Date] = Field(None, description="Date") + time: Optional[Time] = Field(None, description="Time") + shift: Optional[str] = Field(None, max_length=1, description="Shift") + total_cash_paid: Optional[int] = Field(None, description="Total cash paid") + total_contributions: Optional[int] = Field(None, description="Total contributions") + counter_payment: Optional[int] = Field(None, description="Counter payment") + pece_code: Optional[str] = Field(None, max_length=5, description="PECE code") class PedimentoPaymentsUpdate(BaseModel): @@ -48,8 +57,7 @@ class PedimentoPaymentsUpdate(BaseModel): total_cash_paid: Optional[int] = None total_contributions: Optional[int] = None counter_payment: Optional[int] = None - pece_code: Optional[str] = Field(None, max_length=5) - payment_id: Optional[int] = None + pece_code: Optional[str] = Field(None, max_length=5) class PedimentoPaymentsResponse(PedimentoPaymentsBase): diff --git a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_transport_means.py b/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_transport_means.py index 560446f2..f5639249 100644 --- a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_transport_means.py +++ b/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_transport_means.py @@ -15,10 +15,13 @@ class PedimentoTransportMeansBase(BaseModel): departure: Optional[str] = Field(None, max_length=2, description="Departure") -class PedimentoTransportMeansCreate(PedimentoTransportMeansBase): - """Schema for creating a new Pedimento Transport Means""" +class PedimentoTransportMeansCreate(BaseModel): + """Schema for creating a new Pedimento Transport Means - pedimento_id and tenant_id are set by backend""" - pass + destination: Optional[int] = Field(None, description="Destination") + entry_exit: Optional[str] = Field(None, max_length=2, description="Entry/exit") + arrival: Optional[str] = Field(None, max_length=2, description="Arrival") + departure: Optional[str] = Field(None, max_length=2, description="Departure") class PedimentoTransportMeansUpdate(BaseModel): diff --git a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_validation.py b/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_validation.py index 568f784d..649ce3cb 100644 --- a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_validation.py +++ b/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_validation.py @@ -27,10 +27,17 @@ class PedimentoValidationBase(BaseModel): responsible_id: Optional[int] = Field(None, description="Responsible ID") -class PedimentoValidationCreate(PedimentoValidationBase): - """Schema for creating a new Pedimento Validation""" +class PedimentoValidationCreate(BaseModel): + """Schema for creating a new Pedimento Validation - pedimento_id and tenant_id are set by backend""" - pass + validator: Optional[str] = Field(None, max_length=3, description="Validator") + validation_ack: Optional[str] = Field(None, max_length=8, description="Validation acknowledgment") + pre_ack: Optional[str] = Field(None, max_length=8, description="Previous acknowledgment") + line_signature: Optional[str] = Field(None, max_length=50, description="Line signature") + electronic_signature: Optional[str] = Field(None, max_length=999, description="Electronic signature") + certificate_number: Optional[str] = Field(None, max_length=99, description="Certificate number") + validator_id: Optional[int] = Field(None, description="Validator ID") + responsible_id: Optional[int] = Field(None, description="Responsible ID") class PedimentoValidationUpdate(BaseModel): diff --git a/backend/api/v1/modules/a76/pedmientos/dtos/pedimentos.py b/backend/api/v1/modules/a76/pedmientos/dtos/pedimentos.py index 7a89c050..c34dc9ff 100644 --- a/backend/api/v1/modules/a76/pedmientos/dtos/pedimentos.py +++ b/backend/api/v1/modules/a76/pedmientos/dtos/pedimentos.py @@ -5,22 +5,22 @@ from typing import Optional from pydantic import BaseModel, ConfigDict, Field -from .pedimento_config_additional import PedimentoConfigAdditionalCreate -from .pedimento_config_calculations import PedimentoConfigCalculationsCreate -from .pedimento_config_parameters import PedimentoConfigParametersCreate -from .pedimento_config_surcharges import PedimentoConfigSurchargesCreate -from .pedimento_config_update_rectification import PedimentoConfigUpdateRectificationCreate -from .pedimento_config_updates import PedimentoConfigUpdatesCreate -from .pedimento_customs_offices import PedimentoCustomsOfficesCreate -from .pedimento_dates import PedimentoDatesCreate -from .pedimento_decrementables import PedimentoDecrementablesCreate -from .pedimento_incrementables import PedimentoIncrementablesCreate -from .pedimento_indexes import PedimentoIndexesCreate -from .pedimento_payments import PedimentoPaymentsCreate -from .pedimento_rectification_destination import PedimentoRectificationDestinationCreate -from .pedimento_rectification_origin import PedimentoRectificationOriginCreate -from .pedimento_transport_means import PedimentoTransportMeansCreate -from .pedimento_validation import PedimentoValidationCreate +from .pedimento_config_additional import PedimentoConfigAdditionalCreate, PedimentoConfigAdditionalResponse +from .pedimento_config_calculations import PedimentoConfigCalculationsCreate, PedimentoConfigCalculationsResponse +from .pedimento_config_parameters import PedimentoConfigParametersCreate, PedimentoConfigParametersResponse +from .pedimento_config_surcharges import PedimentoConfigSurchargesCreate, PedimentoConfigSurchargesResponse +from .pedimento_config_update_rectification import PedimentoConfigUpdateRectificationCreate, PedimentoConfigUpdateRectificationResponse +from .pedimento_config_updates import PedimentoConfigUpdatesCreate, PedimentoConfigUpdatesResponse +from .pedimento_customs_offices import PedimentoCustomsOfficesCreate, PedimentoCustomsOfficesResponse +from .pedimento_dates import PedimentoDatesCreate, PedimentoDatesResponse +from .pedimento_decrementables import PedimentoDecrementablesCreate, PedimentoDecrementablesResponse +from .pedimento_incrementables import PedimentoIncrementablesCreate, PedimentoIncrementablesResponse +from .pedimento_indexes import PedimentoIndexesCreate, PedimentoIndexesResponse +from .pedimento_payments import PedimentoPaymentsCreate, PedimentoPaymentsResponse +from .pedimento_rectification_destination import PedimentoRectificationDestinationCreate, PedimentoRectificationDestinationResponse +from .pedimento_rectification_origin import PedimentoRectificationOriginCreate, PedimentoRectificationOriginResponse +from .pedimento_transport_means import PedimentoTransportMeansCreate, PedimentoTransportMeansResponse +from .pedimento_validation import PedimentoValidationCreate, PedimentoValidationResponse class OperationType(IntEnum): @@ -50,8 +50,22 @@ class PedimentosBase(BaseModel): usd_value: Optional[Decimal] = Field(None, description="USD value") paid_price: Optional[Decimal] = Field(None, description="Paid price") gross_weight: Optional[Decimal] = Field(None, description="Gross weight") - exchange_rate: Optional[Decimal] = Field(None, description="Exchange rate") + exchange_rate: Optional[Decimal] = Field(None, description="Exchange rate") +class PedimentosCreate(PedimentosBase): + """Schema for creating a new Pedimento""" + + # Override to make required fields non-optional + year: str = Field(..., max_length=2, description="Year") + customs_office: str = Field(..., max_length=2, description="Customs office") + license: str = Field(..., max_length=4, description="License") + pedimento_number: str = Field(..., max_length=7, description="Pedimento number") + client_id: int = Field(..., description="Client ID") + operation_type: int = Field(..., description="Operation type") + pedimento_type: int = Field(..., description="Pedimento type") + regime: str = Field(..., max_length=3, description="Regime") + status: str = Field(..., max_length=30, description="Status") + pedimento_dates: Optional[PedimentoDatesCreate] = None pedimento_decrementables: Optional[PedimentoDecrementablesCreate] = None pedimento_incrementables: Optional[PedimentoIncrementablesCreate] = None @@ -69,37 +83,41 @@ class PedimentosBase(BaseModel): pedimento_config_update_rectification: Optional[PedimentoConfigUpdateRectificationCreate] = None pedimento_config_updates: Optional[PedimentoConfigUpdatesCreate] = None -class PedimentosCreate(PedimentosBase): - """Schema for creating a new Pedimento""" - - # Override to make required fields non-optional - year: str = Field(..., max_length=2, description="Year") - customs_office: str = Field(..., max_length=2, description="Customs office") - license: str = Field(..., max_length=4, description="License") - pedimento_number: str = Field(..., max_length=7, description="Pedimento number") - client_id: int = Field(..., description="Client ID") - operation_type: int = Field(..., description="Operation type") - pedimento_type: int = Field(..., description="Pedimento type") - regime: str = Field(..., max_length=3, description="Regime") - status: str = Field(..., max_length=30, description="Status") - class PedimentosUpdate(BaseModel): """Schema for updating a Pedimento""" - year: Optional[str] = Field(..., max_length=2) - customs_office: Optional[str] = Field(..., max_length=2) - license: Optional[str] = Field(..., max_length=4) - pedimento_number: Optional[str] = Field(..., max_length=7) - client_id: Optional[int] - operation_type: Optional[OperationType] - pedimento_type: Optional[int] - pedimento_code: Optional[str] = Field(..., max_length=2) - regime: Optional[str] = Field(..., max_length=3) - status: Optional[str] = Field(..., max_length=30) + year: Optional[str] = Field(None, max_length=2) + customs_office: Optional[str] = Field(None, max_length=2) + license: Optional[str] = Field(None, max_length=4) + pedimento_number: Optional[str] = Field(None, max_length=7) + client_id: Optional[int] = None + operation_type: Optional[int] = None + pedimento_type: Optional[int] = None + pedimento_code: Optional[str] = Field(None, max_length=2) + regime: Optional[str] = Field(None, max_length=3) + status: Optional[str] = Field(None, max_length=30) usd_value: Optional[Decimal] = None paid_price: Optional[Decimal] = None gross_weight: Optional[Decimal] = None exchange_rate: Optional[Decimal] = None + + # Sub-resources + pedimento_dates: Optional[PedimentoDatesCreate] = None + pedimento_decrementables: Optional[PedimentoDecrementablesCreate] = None + pedimento_incrementables: Optional[PedimentoIncrementablesCreate] = None + pedimento_indexes: Optional[PedimentoIndexesCreate] = None + pedimento_validation: Optional[PedimentoValidationCreate] = None + pedimento_customs_offices: Optional[PedimentoCustomsOfficesCreate] = None + pedimento_payments: Optional[PedimentoPaymentsCreate] = None + pedimento_rectification_destination: Optional[PedimentoRectificationDestinationCreate] = None + pedimento_rectification_origin: Optional[PedimentoRectificationOriginCreate] = None + pedimento_transport_means: Optional[PedimentoTransportMeansCreate] = None + pedimento_config_additional: Optional[PedimentoConfigAdditionalCreate] = None + pedimento_config_calculations: Optional[PedimentoConfigCalculationsCreate] = None + pedimento_config_parameters: Optional[PedimentoConfigParametersCreate] = None + pedimento_config_surcharges: Optional[PedimentoConfigSurchargesCreate] = None + pedimento_config_update_rectification: Optional[PedimentoConfigUpdateRectificationCreate] = None + pedimento_config_updates: Optional[PedimentoConfigUpdatesCreate] = None class PedimentosResponse(PedimentosBase): @@ -108,5 +126,22 @@ class PedimentosResponse(PedimentosBase): id: int tenant_id: int created_at: datetime + + pedimento_dates: Optional[PedimentoDatesResponse] = None + pedimento_decrementables: Optional[PedimentoDecrementablesResponse] = None + pedimento_incrementables: Optional[PedimentoIncrementablesResponse] = None + pedimento_indexes: Optional[PedimentoIndexesResponse] = None + pedimento_validation: Optional[PedimentoValidationResponse] = None + pedimento_customs_offices: Optional[PedimentoCustomsOfficesResponse] = None + pedimento_payments: Optional[PedimentoPaymentsResponse] = None + pedimento_rectification_destination: Optional[PedimentoRectificationDestinationResponse] = None + pedimento_rectification_origin: Optional[PedimentoRectificationOriginResponse] = None + pedimento_transport_means: Optional[PedimentoTransportMeansResponse] = None + pedimento_config_additional: Optional[PedimentoConfigAdditionalResponse] = None + pedimento_config_calculations: Optional[PedimentoConfigCalculationsResponse] = None + pedimento_config_parameters: Optional[PedimentoConfigParametersResponse] = None + pedimento_config_surcharges: Optional[PedimentoConfigSurchargesResponse] = None + pedimento_config_update_rectification: Optional[PedimentoConfigUpdateRectificationResponse] = None + pedimento_config_updates: Optional[PedimentoConfigUpdatesResponse] = None model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a76/pedmientos/models/pedimento_dates.py b/backend/api/v1/modules/a76/pedmientos/models/pedimento_dates.py index a6f1958b..cea9f34d 100644 --- a/backend/api/v1/modules/a76/pedmientos/models/pedimento_dates.py +++ b/backend/api/v1/modules/a76/pedmientos/models/pedimento_dates.py @@ -1,6 +1,6 @@ from datetime import datetime from datetime import time as datetime_time -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional from api.v1.common.base_models import TenantScopedMixin, TimestampMixin from core.database import Base @@ -48,16 +48,16 @@ class PedimentoDates(Base, TenantScopedMixin, TimestampMixin): id: Mapped[int] = mapped_column(Integer) pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False) - entry_date: Mapped[datetime] = mapped_column(DateTime) + entry_date: Mapped[Optional[datetime]] = mapped_column(DateTime) pedimento_date: Mapped[datetime] = mapped_column(DateTime) payment_date: Mapped[datetime] = mapped_column(DateTime) - rectification_payment_date: Mapped[datetime] = mapped_column(DateTime) - extraction_date: Mapped[datetime] = mapped_column(DateTime) - submission_date: Mapped[datetime] = mapped_column(DateTime) - eucan_date: Mapped[datetime] = mapped_column(DateTime) - original_date: Mapped[datetime] = mapped_column(DateTime) - start_date: Mapped[datetime] = mapped_column(DateTime) - end_date: Mapped[datetime] = mapped_column(DateTime) + rectification_payment_date: Mapped[Optional[datetime]] = mapped_column(DateTime) + extraction_date: Mapped[Optional[datetime]] = mapped_column(DateTime) + submission_date: Mapped[Optional[datetime]] = mapped_column(DateTime) + eucan_date: Mapped[Optional[datetime]] = mapped_column(DateTime) + original_date: Mapped[Optional[datetime]] = mapped_column(DateTime) + start_date: Mapped[Optional[datetime]] = mapped_column(DateTime) + end_date: Mapped[Optional[datetime]] = mapped_column(DateTime) capture_date: Mapped[datetime] = mapped_column(DateTime) capture_time: Mapped[datetime_time] = mapped_column(Time) diff --git a/backend/api/v1/modules/a76/pedmientos/models/pedimento_payments.py b/backend/api/v1/modules/a76/pedmientos/models/pedimento_payments.py index f67f06c5..2f636f99 100644 --- a/backend/api/v1/modules/a76/pedmientos/models/pedimento_payments.py +++ b/backend/api/v1/modules/a76/pedmientos/models/pedimento_payments.py @@ -49,7 +49,6 @@ class PedimentoPayments(Base, TenantScopedMixin, TimestampMixin): id: Mapped[int] = mapped_column(Integer) pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False) - payment_id: Mapped[int] = mapped_column(Integer) acknowledgment: Mapped[str] = mapped_column(String(20)) operation_number: Mapped[str] = mapped_column(String(14)) diff --git a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_payments.py b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_payments.py index 02dbfd0d..7078e26f 100644 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_payments.py +++ b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_payments.py @@ -35,10 +35,10 @@ async def list_payments( return payments -@router.get("/{payment_id}", response_model=PedimentoPaymentsResponse) +@router.get("/{id}", response_model=PedimentoPaymentsResponse) async def get_payment( pedimento_id: int, - payment_id: int, + id: int, company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), current_user: Dict[str, Any] = Depends(get_current_user), @@ -47,7 +47,7 @@ async def get_payment( tenant_id = validate_access_to_resource(db, company_id, current_user) payment = PedimentoPaymentsService.get_by_id( - db, payment_id, pedimento_id, tenant_id, company_id + db, id, pedimento_id, tenant_id, company_id ) if not payment: raise HTTPException(status_code=404, detail="Payment not found") @@ -74,10 +74,10 @@ async def create_payment( return payment -@router.put("/{payment_id}", response_model=PedimentoPaymentsResponse) +@router.put("/{id}", response_model=PedimentoPaymentsResponse) async def update_payment( pedimento_id: int, - payment_id: int, + id: int, data: PedimentoPaymentsUpdate, company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), @@ -87,7 +87,7 @@ async def update_payment( tenant_id = validate_access_to_resource(db, company_id, current_user) payment = PedimentoPaymentsService.update( - db, payment_id, pedimento_id, tenant_id, company_id, data + db, id, pedimento_id, tenant_id, company_id, data ) if not payment: raise HTTPException(status_code=404, detail="Payment not found") @@ -95,10 +95,10 @@ async def update_payment( return payment -@router.delete("/{payment_id}", status_code=204) +@router.delete("/{id}", status_code=204) async def delete_payment( pedimento_id: int, - payment_id: int, + id: int, company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), current_user: Dict[str, Any] = Depends(get_current_user), @@ -107,7 +107,7 @@ async def delete_payment( tenant_id = validate_access_to_resource(db, company_id, current_user) success = PedimentoPaymentsService.delete( - db, payment_id, pedimento_id, tenant_id, company_id + db, id, pedimento_id, tenant_id, company_id ) if not success: raise HTTPException(status_code=404, detail="Payment not found") diff --git a/backend/api/v1/modules/a76/pedmientos/services/pedimentos.py b/backend/api/v1/modules/a76/pedmientos/services/pedimentos.py index ddfca359..b2ef0719 100644 --- a/backend/api/v1/modules/a76/pedmientos/services/pedimentos.py +++ b/backend/api/v1/modules/a76/pedmientos/services/pedimentos.py @@ -6,7 +6,8 @@ import logging from typing import Any, Dict, List, Optional from sqlalchemy import desc -from sqlalchemy.orm import Session +from sqlalchemy.orm import Session, joinedload +from sqlalchemy.orm import selectinload from ..dtos.pedimentos import PedimentosCreate, PedimentosUpdate @@ -85,8 +86,31 @@ class PedimentosService: query = query.filter(Pedimentos.year == filters["year"]) total = query.count() + + # Eager load all relationships for the response schema items = ( - query.order_by(desc(Pedimentos.created_at)).offset(skip).limit(limit).all() + query.options( + selectinload(Pedimentos.pedimento_dates), + selectinload(Pedimentos.pedimento_decrementables), + selectinload(Pedimentos.pedimento_incrementables), + selectinload(Pedimentos.pedimento_indexes), + selectinload(Pedimentos.pedimento_validation), + selectinload(Pedimentos.pedimento_customs_offices), + selectinload(Pedimentos.pedimento_payments), + selectinload(Pedimentos.pedimento_rectification_destination), + selectinload(Pedimentos.pedimento_rectification_origin), + selectinload(Pedimentos.pedimento_transport_means), + selectinload(Pedimentos.pedimento_config_additional), + selectinload(Pedimentos.pedimento_config_calculations), + selectinload(Pedimentos.pedimento_config_parameters), + selectinload(Pedimentos.pedimento_config_surcharges), + selectinload(Pedimentos.pedimento_config_update_rectification), + selectinload(Pedimentos.pedimento_config_updates), + ) + .order_by(desc(Pedimentos.created_at)) + .offset(skip) + .limit(limit) + .all() ) return items, total @@ -113,6 +137,26 @@ class PedimentosService: if company_id is not None: query = query.filter(Pedimentos.company_id == company_id) + + # Eager load all relationships for the response schema + query = query.options( + selectinload(Pedimentos.pedimento_dates), + selectinload(Pedimentos.pedimento_decrementables), + selectinload(Pedimentos.pedimento_incrementables), + selectinload(Pedimentos.pedimento_indexes), + selectinload(Pedimentos.pedimento_validation), + selectinload(Pedimentos.pedimento_customs_offices), + selectinload(Pedimentos.pedimento_payments), + selectinload(Pedimentos.pedimento_rectification_destination), + selectinload(Pedimentos.pedimento_rectification_origin), + selectinload(Pedimentos.pedimento_transport_means), + selectinload(Pedimentos.pedimento_config_additional), + selectinload(Pedimentos.pedimento_config_calculations), + selectinload(Pedimentos.pedimento_config_parameters), + selectinload(Pedimentos.pedimento_config_surcharges), + selectinload(Pedimentos.pedimento_config_update_rectification), + selectinload(Pedimentos.pedimento_config_updates), + ) return query.first() @@ -247,23 +291,25 @@ class PedimentosService: # Helper function para actualizar o crear objetos relacionados def update_or_create_related(service_class, model_class, data_attr): - if not hasattr(pedimento_data, data_attr): + # Obtener datos del payload completo (no solo exclude_unset) + full_data = pedimento_data.model_dump() + + if data_attr not in full_data: return - data = getattr(pedimento_data, data_attr) + data = full_data[data_attr] if not data: return existing = service_class.get_by_pedimento_id(db, pedimento_id, tenant_id) if existing: # Actualizar existente - update_dict = data.model_dump(exclude_unset=True) - for field, value in update_dict.items(): - setattr(existing, field, value) + for field, value in data.items(): + if hasattr(existing, field): + setattr(existing, field, value) else: # Crear nuevo - obj_dict = data.model_dump() - obj = model_class(**obj_dict) + obj = model_class(**data) obj.pedimento_id = pedimento_id obj.tenant_id = tenant_id obj.company_id = company_id diff --git a/backend/api/v1/modules/public/reference_data/code_pedimento_regimens/routes.py b/backend/api/v1/modules/public/reference_data/code_pedimento_regimens/routes.py index de7fc176..76533e83 100644 --- a/backend/api/v1/modules/public/reference_data/code_pedimento_regimens/routes.py +++ b/backend/api/v1/modules/public/reference_data/code_pedimento_regimens/routes.py @@ -15,11 +15,22 @@ router = APIRouter(prefix="/code-pedimento-regimens") def list_code_pedimento_regimens( 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"), + code: str = Query(None, description="Filter by code"), + regime: str = Query(None, description="Filter by regime"), + type: str = Query(None, description="Filter by type"), db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user), ): skip = (page - 1) * page_size query = db.query(CodePedimentoRegimen) + + if code is not None: + query = query.filter(CodePedimentoRegimen.pedimento_code == code) + if regime is not None: + query = query.filter(CodePedimentoRegimen.regime == regime) + if type is not None: + query = query.filter(CodePedimentoRegimen.type == type) + items = query.offset(skip).limit(page_size).all() total = query.count() return { diff --git a/frontend/src/lib/api/dashboard/a76/clients-providers.ts b/frontend/src/lib/api/dashboard/a76/clients-providers.ts index 40faa07d..773de3b0 100644 --- a/frontend/src/lib/api/dashboard/a76/clients-providers.ts +++ b/frontend/src/lib/api/dashboard/a76/clients-providers.ts @@ -31,8 +31,8 @@ export interface ClientProvider { domicile_fiscal?: string | null; foreign_tax_id?: string | null; client_or_provider?: string | null; - enabled_disabled?: number; - tenant_id: number; + is_active?: boolean; + tenant_id: number; address?: ClientProviderAddress | null; programs?: ClientProviderPrograms | null; } @@ -46,7 +46,7 @@ export interface ClientProviderBasic { domicile_fiscal?: string | null; foreign_tax_id?: string | null; client_or_provider?: string | null; - enabled_disabled?: number; + is_active?: boolean; tenant_id: number; } @@ -65,7 +65,7 @@ export interface CreateClientProviderData { domicile_fiscal?: string | null; foreign_tax_id?: string | null; client_or_provider?: string | null; - enabled_disabled?: number; + is_active?: boolean; address?: Omit | null; programs?: Omit | null; } @@ -78,7 +78,7 @@ export interface UpdateClientProviderData { domicile_fiscal?: string | null; foreign_tax_id?: string | null; client_or_provider?: string | null; - enabled_disabled?: number; + is_active?: boolean; address?: Partial | null; programs?: Partial | null; } @@ -100,7 +100,7 @@ export const clientsProvidersApi = { page: page.toString(), page_size: pageSize.toString() }); - + if (filters) { Object.entries(filters).forEach(([key, value]) => { if (value !== undefined && value !== null && value !== '') { @@ -108,7 +108,7 @@ export const clientsProvidersApi = { } }); } - + return api.get( `/v1/a76/clients-providers?${params.toString()}` ); @@ -166,7 +166,7 @@ export const clientsProvidersApi = { /** * Crea un nuevo cliente/proveedor - * @param companyId - ID de la compañía + * @param companyId - ID de la compañía * @param data - Datos del cliente/proveedor a crear */ create: (companyId: number, data: CreateClientProviderData) => diff --git a/frontend/src/lib/api/dashboard/a76/customs-brokers.ts b/frontend/src/lib/api/dashboard/a76/customs-brokers.ts index d6a15c7c..17a82200 100644 --- a/frontend/src/lib/api/dashboard/a76/customs-brokers.ts +++ b/frontend/src/lib/api/dashboard/a76/customs-brokers.ts @@ -19,7 +19,7 @@ export interface CustomsBroker { tax_id?: string | null; personal_id?: string | null; position?: string | null; - license?: string | null; + license: string; company?: string | null; contact?: string | null; tenant_id: string; @@ -117,6 +117,14 @@ export const customsBrokersApi = { return api.delete(`/v1/a76/customs-brokers/${brokerKey}?company_id=${companyId}`); }, + /** + * Actualiza la información de un agente aduanal + */ + update: (brokerKey: string, data: CreateCustomsBrokerData) => { + const companyId = data.company_id; + return api.put(`/v1/a76/customs-brokers/${brokerKey}?company_id=${companyId}`, data); + }, + /** * Actualiza la información de VU de un agente aduanal */ diff --git a/frontend/src/lib/api/dashboard/a76/pedimento-dates.ts b/frontend/src/lib/api/dashboard/a76/pedimento-dates.ts index 7e99ef9e..1cbe8ef4 100644 --- a/frontend/src/lib/api/dashboard/a76/pedimento-dates.ts +++ b/frontend/src/lib/api/dashboard/a76/pedimento-dates.ts @@ -17,8 +17,6 @@ export interface PedimentoDates { original_date?: string | null; start_date?: string | null; end_date?: string | null; - capture_date?: string | null; - capture_time?: string | null; created_at: string; } @@ -33,8 +31,6 @@ export interface CreatePedimentoDatesData { original_date?: string | null; start_date?: string | null; end_date?: string | null; - capture_date?: string | null; - capture_time?: string | null; } export interface UpdatePedimentoDatesData { @@ -48,8 +44,6 @@ export interface UpdatePedimentoDatesData { original_date?: string | null; start_date?: string | null; end_date?: string | null; - capture_date?: string | null; - capture_time?: string | null; } export const pedimentoDatesApi = { diff --git a/frontend/src/lib/api/dashboard/a76/pedimento-payments.ts b/frontend/src/lib/api/dashboard/a76/pedimento-payments.ts index cbe39e93..ac5bfa08 100644 --- a/frontend/src/lib/api/dashboard/a76/pedimento-payments.ts +++ b/frontend/src/lib/api/dashboard/a76/pedimento-payments.ts @@ -17,8 +17,7 @@ export interface PedimentoPayments { total_cash_paid?: number | null; total_contributions?: number | null; counter_payment?: number | null; - pece_code?: string | null; - payment_id?: number | null; + pece_code?: string | null; created_at: string; } @@ -33,8 +32,7 @@ export interface CreatePedimentoPaymentsData { total_cash_paid?: number | null; total_contributions?: number | null; counter_payment?: number | null; - pece_code?: string | null; - payment_id?: number | null; + pece_code?: string | null; } export interface UpdatePedimentoPaymentsData { @@ -48,8 +46,7 @@ export interface UpdatePedimentoPaymentsData { total_cash_paid?: number | null; total_contributions?: number | null; counter_payment?: number | null; - pece_code?: string | null; - payment_id?: number | null; + pece_code?: string | null; } export const pedimentoPaymentsApi = { diff --git a/frontend/src/lib/api/dashboard/a76/pedimentos.ts b/frontend/src/lib/api/dashboard/a76/pedimentos.ts index 4532022a..3e93e440 100644 --- a/frontend/src/lib/api/dashboard/a76/pedimentos.ts +++ b/frontend/src/lib/api/dashboard/a76/pedimentos.ts @@ -9,22 +9,45 @@ export interface PedimentoDates { entry_date?: string | null; pedimento_date?: string | null; payment_date?: string | null; + rectification_payment_date?: string | null; + extraction_date?: string | null; + submission_date?: string | null; + eucan_date?: string | null; + original_date?: string | null; + start_date?: string | null; + end_date?: string | null; } export interface PedimentoPayments { - payment_form?: string | null; - bank_identifier?: string | null; + acknowledgment?: string | null; + operation_number?: string | null; + bank_code?: string | null; + cashier?: string | null; + date?: string | null; + time?: string | null; + shift?: string | null; + total_cash_paid?: string | null; + total_contributions?: string | null; + counter_payment?: string | null; + pece_code?: string | null; } export interface PedimentoTransportMeans { - arrival_key?: string | null; - arrival_data?: string | null; - departure_key?: string | null; - departure_data?: string | null; + destination?: number | null; + entry_exit?: string | null; + arrival?: string | null; + departure?: string | null; } export interface PedimentoValidation { - document?: string | null; + validator?: string | null; + validation_ack?: string | null; + pre_ack?: string | null; + line_signature?: string | null; + electronic_signature?: string | null; + certificate_number?: string | null; + validator_id?: number | null; + responsible_id?: number | null; } export interface Pedimento { @@ -49,7 +72,7 @@ export interface Pedimento { pedimento_dates?: PedimentoDates | null; pedimento_payments?: PedimentoPayments | null; pedimento_transport_means?: PedimentoTransportMeans | null; - pedimento_validation?: PedimentoValidation | null; + pedimento_validation?: PedimentoValidation | null; } export interface PedimentoListResponse { diff --git a/frontend/src/lib/components/dashboard/classes/create-edit-dialog.svelte b/frontend/src/lib/components/dashboard/classes/create-edit-dialog.svelte index cac2f932..a61219e4 100644 --- a/frontend/src/lib/components/dashboard/classes/create-edit-dialog.svelte +++ b/frontend/src/lib/components/dashboard/classes/create-edit-dialog.svelte @@ -9,6 +9,7 @@ import { materialTypesApi, type MaterialType } from "$lib/api/dashboard/refrence_data/material_types"; import { companyStore } from "$lib/stores/company.svelte"; import { onMount } from 'svelte'; + import { LoaderCircle, Home } from 'lucide-svelte'; let { open = $bindable(false), @@ -248,21 +249,7 @@ {#if companyStore.activeCompany}
- - - - +

{companyStore.activeCompany.name} @@ -426,26 +413,7 @@ {/snippet} @@ -102,63 +89,15 @@ Acciones - - - - + Editar {#if loading} - - - - + {:else} - - - - - + {/if} Eliminar diff --git a/frontend/src/lib/components/dashboard/clients_and_providers/columns.ts b/frontend/src/lib/components/dashboard/clients_and_providers/columns.ts index cfa37a35..ef8ea700 100644 --- a/frontend/src/lib/components/dashboard/clients_and_providers/columns.ts +++ b/frontend/src/lib/components/dashboard/clients_and_providers/columns.ts @@ -56,10 +56,10 @@ export function createColumns(onSuccess?: () => void): ColumnDef const typeSnippet = createRawSnippet<[{ type: string | null | undefined }]>((getType) => { const { type } = getType(); const displayType = type === 'client' ? 'Cliente' : type === 'provider' ? 'Proveedor' : type === 'both' ? 'Ambos' : 'N/A'; - const colorClass = type === 'client' ? 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300' + const colorClass = type === 'client' ? 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300' : type === 'provider' ? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300' - : type === 'both' ? 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-300' - : 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-300'; + : type === 'both' ? 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-300' + : 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-300'; return { render: () => `${displayType}` }; @@ -81,21 +81,21 @@ export function createColumns(onSuccess?: () => void): ColumnDef } }, { - accessorKey: "enabled_disabled", + accessorKey: "is_active", header: "Estado", cell: ({ row }) => { const statusSnippet = createRawSnippet<[{ status: number | undefined }]>((getStatus) => { const { status } = getStatus(); const isEnabled = status === 1; const statusText = isEnabled ? 'Activo' : 'Inactivo'; - const colorClass = isEnabled - ? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300' + const colorClass = isEnabled + ? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300' : 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300'; return { render: () => `${statusText}` }; }); - return renderSnippet(statusSnippet, { status: row.original.enabled_disabled }); + return renderSnippet(statusSnippet, { status: row.original.is_active }); } }, { diff --git a/frontend/src/lib/components/dashboard/clients_and_providers/create-edit-dialog.svelte b/frontend/src/lib/components/dashboard/clients_and_providers/create-edit-dialog.svelte index 2a85404a..811cba86 100644 --- a/frontend/src/lib/components/dashboard/clients_and_providers/create-edit-dialog.svelte +++ b/frontend/src/lib/components/dashboard/clients_and_providers/create-edit-dialog.svelte @@ -5,6 +5,7 @@ import { Label } from "$lib/components/ui/label"; import { clientsProvidersApi, type ClientProvider, type CreateClientProviderData, type UpdateClientProviderData } from "$lib/api/dashboard/a76/clients-providers"; import { companyStore } from "$lib/stores/company.svelte"; + import { LoaderCircle } from 'lucide-svelte'; let { open = $bindable(false), @@ -24,7 +25,7 @@ domicile_fiscal: "", foreign_tax_id: "", client_or_provider: "client", - enabled_disabled: 1, + is_active: true, // Address fields street: "", neighborhood: "", @@ -51,7 +52,7 @@ domicile_fiscal: item.domicile_fiscal || "", foreign_tax_id: item.foreign_tax_id || "", client_or_provider: item.client_or_provider || "client", - enabled_disabled: item.enabled_disabled ?? 1, + is_active: item.is_active ?? true, street: item.address?.street || "", neighborhood: item.address?.neighborhood || "", city: item.address?.city || "", @@ -70,7 +71,7 @@ domicile_fiscal: "", foreign_tax_id: "", client_or_provider: "client", - enabled_disabled: 1, + is_active: true, street: "", neighborhood: "", city: "", @@ -107,7 +108,7 @@ domicile_fiscal: formData.domicile_fiscal || null, foreign_tax_id: formData.foreign_tax_id || null, client_or_provider: formData.client_or_provider as "client" | "provider" | "both" | null, - enabled_disabled: formData.enabled_disabled, + is_active: formData.is_active, address: { street: formData.street || null, neighborhood: formData.neighborhood || null, @@ -132,7 +133,7 @@ domicile_fiscal: formData.domicile_fiscal || null, foreign_tax_id: formData.foreign_tax_id || null, client_or_provider: formData.client_or_provider as "client" | "provider" | "both" | null, - enabled_disabled: formData.enabled_disabled, + is_active: formData.is_active, address: { street: formData.street || null, neighborhood: formData.neighborhood || null, @@ -185,7 +186,7 @@ domicile_fiscal: "", foreign_tax_id: "", client_or_provider: "client", - enabled_disabled: 1, + is_active: true, street: "", neighborhood: "", city: "", @@ -432,26 +433,7 @@ diff --git a/frontend/src/lib/components/dashboard/clients_and_providers/data-table-actions.svelte b/frontend/src/lib/components/dashboard/clients_and_providers/data-table-actions.svelte index 982cc4dc..888b9488 100644 --- a/frontend/src/lib/components/dashboard/clients_and_providers/data-table-actions.svelte +++ b/frontend/src/lib/components/dashboard/clients_and_providers/data-table-actions.svelte @@ -90,7 +90,7 @@ Ver detalles Editar - {isToggling ? 'Cambiando...' : item.enabled_disabled === 1 ? 'Desactivar' : 'Activar'} + {isToggling ? 'Cambiando...' : item.is_active === true ? 'Desactivar' : 'Activar'} Eliminar diff --git a/frontend/src/lib/components/dashboard/clients_and_providers/delete-dialog.svelte b/frontend/src/lib/components/dashboard/clients_and_providers/delete-dialog.svelte index 2d0963be..f0b8d48c 100644 --- a/frontend/src/lib/components/dashboard/clients_and_providers/delete-dialog.svelte +++ b/frontend/src/lib/components/dashboard/clients_and_providers/delete-dialog.svelte @@ -3,6 +3,7 @@ import * as AlertDialog from "$lib/components/ui/alert-dialog"; import { clientsProvidersApi, type ClientProvider } from "$lib/api/dashboard/a76/clients-providers"; import { companyStore } from "$lib/stores/company.svelte"; + import { LoaderCircle } from 'lucide-svelte'; let { open = $bindable(false), @@ -97,26 +98,7 @@ class="bg-destructive text-destructive-foreground hover:bg-destructive/90" > {#if loading} - - - - + {/if} Eliminar diff --git a/frontend/src/lib/components/dashboard/clients_and_providers/details-dialog.svelte b/frontend/src/lib/components/dashboard/clients_and_providers/details-dialog.svelte index 592a85e7..fe478312 100644 --- a/frontend/src/lib/components/dashboard/clients_and_providers/details-dialog.svelte +++ b/frontend/src/lib/components/dashboard/clients_and_providers/details-dialog.svelte @@ -85,7 +85,7 @@

Estado - {#if item.enabled_disabled === 1} + {#if item.is_active === true} Activo diff --git a/frontend/src/lib/components/dashboard/customs_brokers/data-table-actions.svelte b/frontend/src/lib/components/dashboard/customs_brokers/data-table-actions.svelte index 5a6f7281..3696cbd7 100644 --- a/frontend/src/lib/components/dashboard/customs_brokers/data-table-actions.svelte +++ b/frontend/src/lib/components/dashboard/customs_brokers/data-table-actions.svelte @@ -5,6 +5,7 @@ import type { CustomsBroker } from "./columns.js"; import DetailsDialog from "./details-dialog.svelte"; import DeleteDialog from "./delete-dialog.svelte"; + import EditDialog from "./edit-dialog.svelte"; let { broker, @@ -16,6 +17,7 @@ let showDetailsDialog = $state(false); let showDeleteDialog = $state(false); + let showEditDialog = $state(false); function handleCopyKey() { navigator.clipboard.writeText(broker.broker_key); @@ -31,6 +33,10 @@ showDetailsDialog = true; } + function handleEdit() { + showEditDialog = true; + } + function handleDelete() { showDeleteDialog = true; } @@ -64,6 +70,9 @@ Ver detalles + + Editar + Eliminar @@ -72,4 +81,5 @@ + diff --git a/frontend/src/lib/components/dashboard/customs_brokers/delete-dialog.svelte b/frontend/src/lib/components/dashboard/customs_brokers/delete-dialog.svelte index bf2ffa39..0658bd95 100644 --- a/frontend/src/lib/components/dashboard/customs_brokers/delete-dialog.svelte +++ b/frontend/src/lib/components/dashboard/customs_brokers/delete-dialog.svelte @@ -2,6 +2,7 @@ import { Button } from "$lib/components/ui/button"; import * as AlertDialog from "$lib/components/ui/alert-dialog"; import { customsBrokersApi, type CustomsBroker } from "$lib/api/dashboard/a76/customs-brokers"; + import { LoaderCircle } from 'lucide-svelte'; let { open = $bindable(false), @@ -92,26 +93,7 @@ class="bg-destructive text-destructive-foreground hover:bg-destructive/90" > {#if loading} - - - - + Eliminando... {:else} Eliminar diff --git a/frontend/src/lib/components/dashboard/customs_brokers/edit-dialog.svelte b/frontend/src/lib/components/dashboard/customs_brokers/edit-dialog.svelte new file mode 100644 index 00000000..e5bfeebb --- /dev/null +++ b/frontend/src/lib/components/dashboard/customs_brokers/edit-dialog.svelte @@ -0,0 +1,384 @@ + + + + + + Editar Agente Aduanal + + Modifica los datos del agente aduanal {broker?.broker_key}. + + + +
+ {#if error} +
+ {error} +
+ {/if} + + +
+

Información Básica

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

Información de Contacto

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

Dirección

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

Información Fiscal

+ +
+
+ + +
+ +
+ + +
+
+ +
+ + +
+
+ + + + + +
+
+
diff --git a/frontend/src/lib/components/dashboard/pedimentos/data-table-actions.svelte b/frontend/src/lib/components/dashboard/pedimentos/data-table-actions.svelte index 01f9bba9..1aa00270 100644 --- a/frontend/src/lib/components/dashboard/pedimentos/data-table-actions.svelte +++ b/frontend/src/lib/components/dashboard/pedimentos/data-table-actions.svelte @@ -2,6 +2,7 @@ import { Button } from "$lib/components/ui/button"; import * as DropdownMenu from "$lib/components/ui/dropdown-menu"; import { pedimentosApi, type Pedimento } from "$lib/api/dashboard/a76/pedimentos"; + import { EllipsisVertical, Pencil, LoaderCircle, Trash2 } from 'lucide-svelte'; let { item, @@ -62,21 +63,7 @@ {#snippet child({ props })} {/snippet} @@ -84,63 +71,15 @@ Acciones - - - - + Editar {#if loading} - - - - + {:else} - - - - - + {/if} Eliminar diff --git a/frontend/src/lib/components/dashboard/pedimentos/edit/dates-tab-form.svelte b/frontend/src/lib/components/dashboard/pedimentos/edit/dates-tab-form.svelte index e8847fc9..5377d232 100644 --- a/frontend/src/lib/components/dashboard/pedimentos/edit/dates-tab-form.svelte +++ b/frontend/src/lib/components/dashboard/pedimentos/edit/dates-tab-form.svelte @@ -1,90 +1,40 @@ @@ -113,15 +60,8 @@ - {#if loading} -
- - - -
- {:else} -
-
+
+
@@ -220,29 +160,8 @@ type="date" bind:value={formData.end_date} /> -
- - -
- - -
- - -
- - -
+
- {/if} - - + + \ No newline at end of file diff --git a/frontend/src/lib/components/dashboard/pedimentos/edit/general-tab-form.svelte b/frontend/src/lib/components/dashboard/pedimentos/edit/general-tab-form.svelte index 02123c1b..6c7e5ffc 100644 --- a/frontend/src/lib/components/dashboard/pedimentos/edit/general-tab-form.svelte +++ b/frontend/src/lib/components/dashboard/pedimentos/edit/general-tab-form.svelte @@ -2,20 +2,178 @@ import * as Card from '$lib/components/ui/card'; import { Input } from '$lib/components/ui/input'; import { Label } from '$lib/components/ui/label'; + import * as Select from '$lib/components/ui/select'; import type { Pedimento } from '$lib/api/dashboard/a76/pedimentos'; + import type { PedimentoCode } from '$lib/api/dashboard/refrence_data/pedimento_codes'; + import type { CustomsSection } from '$lib/api/dashboard/refrence_data/customs_sections'; + import type { CustomsBroker } from '$lib/api/dashboard/a76/customs-brokers'; + import type { ClientProvider } from '$lib/api/dashboard/a76/clients-providers'; + import type { CodePedimentoRegimen } from '$lib/api/dashboard/refrence_data/code_pedimento_regimens'; let { pedimento, - formData = $bindable() + formData = $bindable(), + pedimentoCodes = [], + customsSections = [], + customsBrokers = [], + clients = [], + codePedimentoRegimens = [] }: { pedimento: Pedimento | null; formData?: any; + pedimentoCodes?: PedimentoCode[]; + customsSections?: CustomsSection[]; + customsBrokers?: CustomsBroker[]; + clients?: ClientProvider[]; + codePedimentoRegimens?: CodePedimentoRegimen[]; } = $props(); + // Extraer regímenes únicos de codePedimentoRegimens + const uniqueRegimens = $derived( + Array.from(new Set(codePedimentoRegimens.map(r => r.regimen_code).filter((code): code is string => code !== null))) + .sort() + .map(code => ({ code, label: code })) + ); + + // Funciones de mapeo entre type_code (E/I) y operation_type (1/2) + function typeCodeToOperationType(typeCode: string | null | undefined): number | null { + if (!typeCode) return null; + // E = Exportación = 1, I = Importación = 2 + if (typeCode.toUpperCase() === 'E') return 1; + if (typeCode.toUpperCase() === 'I') return 2; + return null; + } + + function operationTypeToTypeCode(operationType: number | null | undefined): string | null { + if (operationType === null || operationType === undefined) return null; + // 1 = Exportación = E, 2 = Importación = I + if (operationType === 1) return 'E'; + if (operationType === 2) return 'I'; + return null; + } + + // Opciones filtradas para Régimen y Tipo de Operación basadas en las selecciones actuales + // NOTA: La Clave NO se filtra, siempre muestra todas las opciones + + const filteredRegimens = $derived(() => { + // Si hay clave o tipo de operación seleccionado, filtrar + if (formData.pedimento_code || formData.operation_type !== null) { + const matches = codePedimentoRegimens.filter(r => { + const matchesCode = !formData.pedimento_code || r.pedimento_code === formData.pedimento_code; + const matchesType = formData.operation_type === null || r.type_code === operationTypeToTypeCode(formData.operation_type); + return matchesCode && matchesType; + }); + const validRegimens = new Set(matches.map(m => m.regimen_code).filter((code): code is string => code !== null)); + return Array.from(validRegimens).sort().map(code => ({ code, label: code })); + } + return uniqueRegimens; + }); + + const filteredOperationTypes = $derived(() => { + // Si hay clave o régimen seleccionado, filtrar + if (formData.pedimento_code || formData.regime) { + const matches = codePedimentoRegimens.filter(r => { + const matchesCode = !formData.pedimento_code || r.pedimento_code === formData.pedimento_code; + const matchesRegime = !formData.regime || r.regimen_code === formData.regime; + return matchesCode && matchesRegime; + }); + const validTypes = new Set(matches.map(m => typeCodeToOperationType(m.type_code)).filter(t => t !== null)); + return operationOptions.filter(opt => validTypes.has(opt.value)); + } + return operationOptions; + }); + + // Reactive synchronization between Clave, Régimen, and Tipo de Operación + // REGLA: La Clave es el campo principal y NUNCA se modifica automáticamente + // Solo se auto-llenan Régimen y Tipo de Operación basándose en la Clave + + // Cuando cambia la Clave del Pedimento + $effect(() => { + const currentCode = formData.pedimento_code; + if (!currentCode) return; + + const matches = codePedimentoRegimens.filter(r => r.pedimento_code === currentCode); + + if (matches.length === 0) return; + + // Verificar si los valores actuales de régimen y tipo son válidos para esta clave + const currentIsValid = matches.some(m => { + const matchesRegime = !formData.regime || m.regimen_code === formData.regime; + const matchesType = formData.operation_type === null || m.type_code === operationTypeToTypeCode(formData.operation_type); + return matchesRegime && matchesType; + }); + + // Si los valores actuales son válidos, NO auto-llenar + if (currentIsValid && (formData.regime || formData.operation_type !== null)) { + return; + } + + // Si solo hay un match y no hay valores válidos, auto-llenar + if (matches.length === 1) { + const match = matches[0]; + if (match.regimen_code && formData.regime !== match.regimen_code) { + formData.regime = match.regimen_code; + } + const expectedOpType = typeCodeToOperationType(match.type_code); + if (expectedOpType !== null && formData.operation_type !== expectedOpType) { + formData.operation_type = expectedOpType; + } + } + }); + + // Cuando cambia el Régimen + $effect(() => { + const currentRegime = formData.regime; + if (!currentRegime) return; + + const matches = codePedimentoRegimens.filter(r => r.regimen_code === currentRegime); + + if (matches.length === 0) return; + + // Si hay clave seleccionada, solo validar (NO auto-llenar tipo de operación) + if (formData.pedimento_code) { + const exactMatch = matches.find(m => m.pedimento_code === formData.pedimento_code); + } + // Si hay tipo de operación pero no clave, no hacer nada + // (el usuario debe seleccionar la clave primero) + }); + + // Cuando cambia el Tipo de Operación + $effect(() => { + const currentType = formData.operation_type; + if (currentType === null || currentType === undefined) return; + + const expectedTypeCode = operationTypeToTypeCode(currentType); + const matches = codePedimentoRegimens.filter(r => r.type_code === expectedTypeCode); + + if (matches.length === 0) return; + + // Si hay clave seleccionada, actualizar régimen (forzar si no hay match exacto) + if (formData.pedimento_code) { + const exactMatch = matches.find(m => m.pedimento_code === formData.pedimento_code); + if (exactMatch?.regimen_code && formData.regime !== exactMatch.regimen_code) { + formData.regime = exactMatch.regimen_code; + } else if (!exactMatch) { + // No hay match exacto - buscar cualquier match con la clave actual + const allMatchesForClave = codePedimentoRegimens.filter(r => r.pedimento_code === formData.pedimento_code); + if (allMatchesForClave.length > 0) { + // Forzar el régimen al primer match disponible para esta clave + const firstMatch = allMatchesForClave[0]; + if (firstMatch.regimen_code) formData.regime = firstMatch.regimen_code; + } + } + } + // Si hay régimen pero no clave, no hacer nada + // (el usuario debe seleccionar la clave primero) + }); + + // Obtener el año actual (últimos 2 dígitos) + const currentYear = String(new Date().getFullYear()).slice(-2); + // Inicializar formData con los valores del pedimento (o vacío si es null) if (!formData) { formData = { - year: pedimento?.year || '', + year: pedimento?.year || currentYear, customs_office: pedimento?.customs_office || '', license: pedimento?.license || '', pedimento_number: pedimento?.pedimento_number || '', @@ -32,6 +190,18 @@ }; } + // Asegurar que el año siempre esté actualizado con el año actual + $effect(() => { + if (formData && !pedimento?.year) { + formData.year = currentYear; + } + }); + + const operationOptions = [ + { value: 1, label: 'Exportación' }, + { value: 2, label: 'Importación' }, + ]; + const statusOptions = [ { value: 'MODIFICABLE', label: 'Modificable' }, { value: 'ESPERA FIRMA PREVIO', label: 'Espera de firma previo' }, @@ -71,6 +241,8 @@ placeholder="23" maxlength={2} class="text-center" + disabled + readonly />
@@ -78,15 +250,28 @@
-
-
+
- + formData.customs_office = v ?? ''} + > + + + {formData.customs_office || 'Sel...'} + + + + {#each customsSections as section} + + + {section.customs_code} - {section.section_name} + + + {/each} + +
@@ -95,13 +280,26 @@
- + formData.license = v ?? ''} + > + + + {formData.license || 'Sel...'} + + + + {#each customsBrokers as broker} + + + {broker.broker_key} - {broker.name || ''} + + + {/each} + +
@@ -123,24 +321,27 @@
- - -
- - -
- - + + formData.client_id = v ? Number(v) : null} + > + + + {clients.find(c => c.id === formData.client_id)?.name || 'Seleccionar cliente...'} + + + + {#each clients as client} + + + {client.name} + + + {/each} + +
@@ -154,39 +355,99 @@ />
- -
- - -
+
- -
- - -
+ +
+ +
+ + formData.pedimento_code = v ?? ''} + > + + + {pedimentoCodes.find(o => o.code === formData.pedimento_code)?.code || 'Sel...'} + + + + {#each pedimentoCodes as code} + + + {code.code} - {code.description} + + + {/each} + + +
+ + +
+ + formData.regime = v ?? ''} + > + + + {formData.regime || 'Sel...'} + + + + {#each filteredRegimens() as regimen} + + + {regimen.code} + + + {/each} + + +
+ + +
+ + formData.operation_type = v ? Number(v) : null} + > + + {operationOptions.find(o => o.value === formData.operation_type)?.label || 'Seleccionar...'} + + + {#each filteredOperationTypes() as option} + + {/each} + + +
+
+ +
- + + {statusOptions.find(o => o.value === formData.status)?.label || 'Seleccionar...'} + + + {#each statusOptions as option} + + {/each} + +
diff --git a/frontend/src/lib/components/dashboard/pedimentos/edit/payments-tab-form.svelte b/frontend/src/lib/components/dashboard/pedimentos/edit/payments-tab-form.svelte index 430726a4..fb004236 100644 --- a/frontend/src/lib/components/dashboard/pedimentos/edit/payments-tab-form.svelte +++ b/frontend/src/lib/components/dashboard/pedimentos/edit/payments-tab-form.svelte @@ -1,106 +1,54 @@ @@ -113,145 +61,128 @@ - {#if loading} -
- - -
- {:else} -
-
- -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
+
+
+ +
+ +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+
- {/if} +
diff --git a/frontend/src/lib/components/dashboard/pedimentos/edit/transport-tab-form.svelte b/frontend/src/lib/components/dashboard/pedimentos/edit/transport-tab-form.svelte index 3c42a633..17ffeeb1 100644 --- a/frontend/src/lib/components/dashboard/pedimentos/edit/transport-tab-form.svelte +++ b/frontend/src/lib/components/dashboard/pedimentos/edit/transport-tab-form.svelte @@ -1,74 +1,40 @@ @@ -81,61 +47,52 @@ - {#if loading} -
- - - - -
- {:else} -
-
- -
- - -
+
+
+ +
+ + +
- -
- - -
+ +
+ + +
- -
- - -
+ +
+ + +
- -
- - -
+ +
+ +
- {/if} +
diff --git a/frontend/src/lib/components/dashboard/pedimentos/edit/validation-tab-form.svelte b/frontend/src/lib/components/dashboard/pedimentos/edit/validation-tab-form.svelte index baabf978..3c5d8c35 100644 --- a/frontend/src/lib/components/dashboard/pedimentos/edit/validation-tab-form.svelte +++ b/frontend/src/lib/components/dashboard/pedimentos/edit/validation-tab-form.svelte @@ -1,79 +1,39 @@ @@ -98,110 +56,96 @@ - {#if loading} -
- - - - -
- {:else} -
-
- -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
+
+
+ +
+ +
- +
- + + +
+ + +
+ + +
+ + +
+
-
+
-