feat: Enhance invoice management with operation and invoice type filters, update dialog defaults, and modify routes for improved functionality

This commit is contained in:
2025-12-12 08:44:09 -06:00
parent e5f6162ffb
commit a07aeb7b12
11 changed files with 238 additions and 60 deletions

View File

@@ -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(

View File

@@ -44,6 +44,9 @@ class InvoiceService:
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']}%"))
@@ -53,6 +56,10 @@ class InvoiceService:
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

View File

@@ -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)

View File

@@ -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,

View File

@@ -30,47 +30,47 @@ seed = [
),
# === TIPOS DE EXPORTACION ===
("DONAC", "DONACION", "", "both", "both"),
("EXDEF", "EXPORTACION DEFINITIVA", "", "material", "both"),
("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",
"both",
"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.",
"both",
"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",
"both",
"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",
"both",
"exp",
),
("SCRAP", "SCRAP", "", "both", "both"),
("SCRAP", "SCRAP", "", "both", "exp"),
(
"VEMEX",
"VENTAS EN MEXICO",
"ESTE PROCESO CONSISTE EN LA VENTA EN EL MERCADO NACIONAL DE LOS PRODUCTOS.",
"both",
"both",
"exp",
),
("VIRTU", "VIRTUALES", "", "material", "both"),
("VIRTU", "VIRTUALES", "", "material", "exp"),
# === ACTIVOS FIJOS (AMBAS OPERACIONES) ===
("AFIJO", "ACTIVO FIJO", "", "fixed asset", "both"),
("REEXP", "REEXPEDICION", "", "fixed asset", "both"),
("AFIJO", "ACTIVO FIJO", "", "fixed asset", "exp"),
("REEXP", "REEXPEDICION", "", "fixed asset", "exp"),
]