feat: Implement Document Types for Digitization module

- Added backend API for managing document types related to digitization, including CRUD operations.
- Created DTOs and models for DocumentTypeDigitization with validation using Pydantic.
- Updated frontend API service to interact with the new document types API.
- Refactored existing forms in the frontend to load and manage document types effectively.
- Enhanced data loading and state management in various components related to pedimentos.
This commit is contained in:
Galindo97
2026-01-02 14:16:09 -06:00
parent 57b7e6dcbf
commit 0b4c385aeb
13 changed files with 890 additions and 228 deletions

View File

@@ -0,0 +1 @@
# Document Types for Digitization module

View File

@@ -0,0 +1,32 @@
from pydantic import BaseModel, Field
class DocumentTypeDigitizationBase(BaseModel):
"""Base schema for Document Type Digitization"""
code: str = Field(..., max_length=10, description="Código del tipo de documento")
description: str = Field(..., description="Descripción del tipo de documento")
active: bool = Field(default=True, description="Indica si el tipo está activo")
class DocumentTypeDigitizationCreate(DocumentTypeDigitizationBase):
"""Schema for creating a Document Type Digitization"""
pass
class DocumentTypeDigitizationUpdate(BaseModel):
"""Schema for updating a Document Type Digitization"""
code: str | None = Field(None, max_length=10)
description: str | None = None
active: bool | None = None
class DocumentTypeDigitizationResponse(DocumentTypeDigitizationBase):
"""Schema for Document Type Digitization response"""
id: int
class Config:
from_attributes = True

View File

@@ -0,0 +1,25 @@
from api.v1.common.base_models import TenantScopedMixin, TimestampMixin
from core.database import Base
from sqlalchemy import Boolean, Integer, PrimaryKeyConstraint, String, Text, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column
class DocumentTypeDigitization(Base, TenantScopedMixin, TimestampMixin):
"""Tipos de documentos para digitalización de pedimentos"""
__tablename__ = "document_types_digitization"
__table_args__ = (
PrimaryKeyConstraint("id", name="document_types_digitization_pkey"),
UniqueConstraint(
"tenant_id",
"company_id",
"code",
name="document_types_digitization_code_key",
),
{"schema": "a76"},
)
id: Mapped[int] = mapped_column(Integer)
code: Mapped[str] = mapped_column(String(10), nullable=False, index=True)
description: Mapped[str] = mapped_column(Text, nullable=False)
active: Mapped[bool] = mapped_column(Boolean, default=True)

View File

@@ -0,0 +1,170 @@
from typing import List
from api.v1.modules.a76.doc_types_dig.dto import (
DocumentTypeDigitizationCreate,
DocumentTypeDigitizationResponse,
DocumentTypeDigitizationUpdate,
)
from api.v1.modules.a76.doc_types_dig.models import DocumentTypeDigitization
from core.database import get_core_db
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy import select
from sqlalchemy.orm import Session
router = APIRouter(prefix="/document-types-digitization", tags=["Document Types Digitization"])
@router.get("", response_model=List[DocumentTypeDigitizationResponse])
def get_all_document_types(
active_only: bool = True,
db: Session = Depends(get_core_db),
):
"""
Obtener todos los tipos de documentos para digitalización
Args:
active_only: Si es True, solo devuelve los tipos activos
"""
query = select(DocumentTypeDigitization)
if active_only:
query = query.where(DocumentTypeDigitization.active == True)
query = query.order_by(DocumentTypeDigitization.code)
result = db.execute(query)
document_types = result.scalars().all()
return document_types
@router.get("/{document_type_id}", response_model=DocumentTypeDigitizationResponse)
def get_document_type(
document_type_id: int,
db: Session = Depends(get_core_db),
):
"""Obtener un tipo de documento por ID"""
result = db.execute(
select(DocumentTypeDigitization).where(DocumentTypeDigitization.id == document_type_id)
)
document_type = result.scalar_one_or_none()
if not document_type:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Tipo de documento con ID {document_type_id} no encontrado"
)
return document_type
@router.get("/by-code/{code}", response_model=DocumentTypeDigitizationResponse)
def get_document_type_by_code(
code: str,
db: Session = Depends(get_core_db),
):
"""Obtener un tipo de documento por código"""
result = db.execute(
select(DocumentTypeDigitization).where(DocumentTypeDigitization.code == code)
)
document_type = result.scalar_one_or_none()
if not document_type:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Tipo de documento con código {code} no encontrado"
)
return document_type
@router.post("", response_model=DocumentTypeDigitizationResponse, status_code=status.HTTP_201_CREATED)
def create_document_type(
document_type_data: DocumentTypeDigitizationCreate,
db: Session = Depends(get_core_db),
):
"""Crear un nuevo tipo de documento"""
# Verificar si el código ya existe
result = db.execute(
select(DocumentTypeDigitization).where(DocumentTypeDigitization.code == document_type_data.code)
)
existing = result.scalar_one_or_none()
if existing:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Ya existe un tipo de documento con el código {document_type_data.code}"
)
new_document_type = DocumentTypeDigitization(**document_type_data.model_dump())
db.add(new_document_type)
db.commit()
db.refresh(new_document_type)
return new_document_type
@router.put("/{document_type_id}", response_model=DocumentTypeDigitizationResponse)
def update_document_type(
document_type_id: int,
document_type_data: DocumentTypeDigitizationUpdate,
db: Session = Depends(get_core_db),
):
"""Actualizar un tipo de documento existente"""
result = db.execute(
select(DocumentTypeDigitization).where(DocumentTypeDigitization.id == document_type_id)
)
document_type = result.scalar_one_or_none()
if not document_type:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Tipo de documento con ID {document_type_id} no encontrado"
)
# Actualizar solo los campos proporcionados
update_data = document_type_data.model_dump(exclude_unset=True)
# Verificar si el nuevo código ya existe (si se está actualizando)
if "code" in update_data and update_data["code"] != document_type.code:
result = db.execute(
select(DocumentTypeDigitization).where(DocumentTypeDigitization.code == update_data["code"])
)
existing = result.scalar_one_or_none()
if existing:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Ya existe un tipo de documento con el código {update_data['code']}"
)
for field, value in update_data.items():
setattr(document_type, field, value)
db.commit()
db.refresh(document_type)
return document_type
@router.delete("/{document_type_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_document_type(
document_type_id: int,
db: Session = Depends(get_core_db),
):
"""Eliminar un tipo de documento (soft delete, marca como inactivo)"""
result = db.execute(
select(DocumentTypeDigitization).where(DocumentTypeDigitization.id == document_type_id)
)
document_type = result.scalar_one_or_none()
if not document_type:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Tipo de documento con ID {document_type_id} no encontrado"
)
# Soft delete - solo marcar como inactivo
document_type.active = False
db.commit()
return None

View File

@@ -3,11 +3,10 @@ from typing import TYPE_CHECKING
from api.v1.common.base_models import TenantScopedMixin, TimestampMixin
from core.database import Base
from sqlalchemy import (
Boolean,
ForeignKeyConstraint,
Integer,
PrimaryKeyConstraint,
SmallInteger,
String,
UniqueConstraint,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
@@ -38,12 +37,12 @@ class PedimentoConfigAdditional(Base, TenantScopedMixin, TimestampMixin):
id: Mapped[int] = mapped_column(Integer)
pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False)
add_po_identifier: Mapped[int] = mapped_column(SmallInteger)
do_not_exempt_norms_complement_x: Mapped[int] = mapped_column(SmallInteger)
manual_pedimento_year: Mapped[str] = mapped_column(String(2))
enable_import_invoice_recipient: Mapped[int] = mapped_column(SmallInteger)
send_502_validation_file_for_consolidated: Mapped[int] = mapped_column(SmallInteger)
add_remove_norms: Mapped[int] = mapped_column(SmallInteger)
add_po_identifier: Mapped[bool] = mapped_column(Boolean, default=False)
do_not_exempt_norms_complement_x: Mapped[bool] = mapped_column(Boolean, default=False)
manual_pedimento_year: Mapped[int] = mapped_column(Integer, nullable=True)
enable_import_invoice_recipient: Mapped[bool] = mapped_column(Boolean, default=False)
send_502_validation_file_for_consolidated: Mapped[bool] = mapped_column(Boolean, default=False)
add_remove_norms: Mapped[bool] = mapped_column(Boolean, default=False)
pedimento: Mapped["Pedimentos"] = relationship(
"Pedimentos", back_populates="pedimento_config_additional"

View File

@@ -14,6 +14,7 @@ from .clients_and_providers import router as client_and_provider_router
from .general_catalogs.company import router as company_router
from .country_rule_oct.routes import router as country_rule_oct_router
from .transportation.drivers.routes import router as drivers_router
from .doc_types_dig.routes import router as doc_types_dig_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
@@ -73,6 +74,7 @@ router.include_router(trailers_router, prefix="/a76", tags=["a76 / trailers"])
router.include_router(
customs_broker_router, prefix="/a76", tags=["a76 / customs_broker"]
)
router.include_router(doc_types_dig_router, prefix="/a76", tags=["a76 / document_types_digitization"])
router.include_router(drivers_router, prefix="/a76", tags=["a76 / drivers"])
router.include_router(transporters_router, prefix="/a76",
tags=["a76 / transporters"])