Files
plantillas-proyectos/backend/api/v1/modules/a76/GClass/routes.py
Kevin Rosales 38d86531e8 feat: Add comprehensive A76 modules with database relationships
 New Features:
- Company module: Single company management with comprehensive business info
- Client & Provider module: Manages clients/providers with address/program relationships
- GParts module: Parts/components management for SCAII, SCAF, and WINSAAI systems
- GClass module: Class classifications for SCAII and SCAF with tariff information

🔗 Database Relationships:
- GPart ↔ GClass: Composite key relationship (client_key, part_class ↔ class_code)
- GPart → Country: Foreign key to public.countries (country_of_origin)
- GPart → CurrencyType: Foreign key to public.currency_types (currency_key)
- GClass → MaterialType: Foreign key to public.material_types (material_key)

📊 API Endpoints Added:

Company Module (/company):
- POST / - Create company
- GET / - Get single company

Client & Provider Module (/clients-providers):
- POST / - Create client/provider
- GET / - List all with pagination
- GET /clients - List only clients
- GET /providers - List only providers
- GET /search/rfc/{rfc} - Search by RFC
- GET /{client_id} - Get by ID
- PUT /{client_id} - Update client/provider
- DELETE /{client_id} - Delete client/provider
- PATCH /{client_id}/toggle-status - Toggle status
- GET /{client_id}/address - Get address info
- GET /{client_id}/programs - Get programs info
- GET /{client_id}/basic - Get basic info

GParts Module (/parts):
- POST / - Create part
- GET / - List all with pagination and filters
- GET /client/{client_key} - Get parts by client
- GET /search/fraction/{fraction} - Search by tariff fraction
- GET /search/supplier/{supplier} - Search by supplier
- GET /search/country/{country_code} - Search by country
- GET /statistics - Get parts statistics
- GET /{client_key}/{part_number} - Get specific part
- PUT /{client_key}/{part_number} - Update part
- DELETE /{client_key}/{part_number} - Delete part
- PATCH /{client_key}/{part_number}/toggle-status - Toggle status
- GET /{client_key}/{part_number}/basic - Get basic info
- GET /{client_key}/{part_number}/regulatory - Get regulatory info

GClass Module (/classes):
- POST / - Create class
- GET / - List all with pagination and filters
- GET /client/{client_key} - Get classes by client
- GET /search/fraction/{fraction} - Search by tariff fraction
- GET /search/material/{material_key} - Search by material
- GET /search/unit-measure/{unit_of_measure} - Search by unit of measure
- GET /search/physical-review/{physical_review} - Search by physical review status
- GET /statistics - Get class statistics
- GET /{client_key}/{class_code} - Get specific class
- PUT /{client_key}/{class_code} - Update class
- DELETE /{client_key}/{class_code} - Delete class
- GET /{client_key}/{class_code}/basic - Get basic info
- GET /{client_key}/{class_code}/tariff - Get tariff information

🏗️ Architecture:
- Modular design with models, DTOs, services, and routes for each entity
- English field names with composite primary keys where applicable
- Comprehensive CRUD operations with specialized search endpoints
- SQLAlchemy relationships with proper foreign key constraints
- Type-safe DTOs with Pydantic validation

📝 Documentation:
- RELATIONSHIPS.md: Complete documentation of database relationships
- Detailed type hints and comprehensive service methods
- Consistent patterns across all modules for maintainability
2025-11-04 21:48:05 -06:00

262 lines
8.0 KiB
Python

"""
Endpoints API para gestión de clases SCAII y SCAF
"""
from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy.orm import Session
from typing import List, Optional
from core.database import get_core_db
from core.security import get_current_user, has_role
from .service import ClassService
from .dto import (
ClassCreateDTO,
ClassUpdateDTO,
ClassResponseDTO,
ClassBasicDTO,
ClassListDTO,
ClassSearchDTO
)
router = APIRouter(prefix="/classes", tags=["Classes"])
@router.post("/", response_model=ClassResponseDTO, status_code=status.HTTP_201_CREATED)
async def create_class(
class_data: ClassCreateDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Create a new class in the system
"""
service = ClassService(db)
return service.create_class(class_data)
@router.get("/", response_model=ClassListDTO)
async def list_classes(
skip: int = Query(0, ge=0, description="Number of records to skip"),
limit: int = Query(100, ge=1, le=1000, description="Maximum number of records to return"),
client_key: Optional[int] = Query(None, description="Filter by client key"),
class_code: Optional[str] = Query(None, description="Search by class code"),
description: Optional[str] = Query(None, description="Search in descriptions"),
material_key: Optional[str] = Query(None, description="Filter by material key"),
fraction: Optional[str] = Query(None, description="Filter by tariff fraction"),
physical_review: Optional[int] = Query(None, description="Filter by physical review indicator"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
List classes with optional filters and pagination
"""
service = ClassService(db)
search_params = ClassSearchDTO(
client_key=client_key,
class_code=class_code,
description=description,
material_key=material_key,
fraction=fraction,
physical_review=physical_review
)
return service.list_classes(skip, limit, search_params)
@router.get("/client/{client_key}", response_model=List[ClassBasicDTO])
async def get_classes_by_client(
client_key: int,
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 all classes for a specific client
"""
service = ClassService(db)
return service.search_by_client(client_key, skip, limit)
@router.get("/search/fraction/{fraction}", response_model=List[ClassBasicDTO])
async def search_by_fraction(
fraction: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Search classes by tariff fraction
"""
service = ClassService(db)
return service.search_by_fraction(fraction)
@router.get("/search/material/{material_key}", response_model=List[ClassBasicDTO])
async def search_by_material(
material_key: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Search classes by material key
"""
service = ClassService(db)
return service.search_by_material(material_key)
@router.get("/search/unit-measure/{unit_of_measure}", response_model=List[ClassBasicDTO])
async def get_classes_by_unit_measure(
unit_of_measure: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Get classes by unit of measure
"""
service = ClassService(db)
return service.get_classes_by_unit_measure(unit_of_measure)
@router.get("/search/physical-review/{physical_review}", response_model=List[ClassBasicDTO])
async def get_classes_by_physical_review(
physical_review: int,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Get classes by physical review indicator
"""
service = ClassService(db)
return service.get_classes_by_physical_review(physical_review)
@router.get("/statistics", response_model=dict)
async def get_classes_statistics(
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Get basic classes statistics
"""
service = ClassService(db)
return service.get_classes_statistics()
@router.get("/{client_key}/{class_code}", response_model=ClassResponseDTO)
async def get_class(
client_key: int,
class_code: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Get class by composite key (client_key + class_code)
"""
service = ClassService(db)
class_obj = service.get_class(client_key, class_code)
if not class_obj:
raise HTTPException(
status_code=404,
detail=f"Class with client_key '{client_key}' and class_code '{class_code}' not found"
)
return class_obj
@router.put("/{client_key}/{class_code}", response_model=ClassResponseDTO)
async def update_class(
client_key: int,
class_code: str,
class_data: ClassUpdateDTO,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Update class information
"""
service = ClassService(db)
class_obj = service.update_class(client_key, class_code, class_data)
if not class_obj:
raise HTTPException(
status_code=404,
detail=f"Class with client_key '{client_key}' and class_code '{class_code}' not found"
)
return class_obj
@router.delete("/{client_key}/{class_code}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_class(
client_key: int,
class_code: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Delete class from the system
Note: This will completely remove the class from the system.
"""
service = ClassService(db)
if not service.delete_class(client_key, class_code):
raise HTTPException(
status_code=404,
detail=f"Class with client_key '{client_key}' and class_code '{class_code}' not found"
)
# Endpoints específicos para información detallada
@router.get("/{client_key}/{class_code}/basic", response_model=ClassBasicDTO)
async def get_class_basic_info(
client_key: int,
class_code: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Get basic information for a class
"""
service = ClassService(db)
class_obj = service.get_class(client_key, class_code)
if not class_obj:
raise HTTPException(
status_code=404,
detail=f"Class with client_key '{client_key}' and class_code '{class_code}' not found"
)
return ClassBasicDTO(
client_key=class_obj.client_key,
class_code=class_obj.class_code,
description_spanish=class_obj.description_spanish,
description_english=class_obj.description_english,
material_key=class_obj.material_key,
fraction=class_obj.fraction
)
@router.get("/{client_key}/{class_code}/tariff", response_model=dict)
async def get_class_tariff_info(
client_key: int,
class_code: str,
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Get tariff information for a class (fractions, IVA exempt, etc.)
"""
service = ClassService(db)
class_obj = service.get_class(client_key, class_code)
if not class_obj:
raise HTTPException(
status_code=404,
detail=f"Class with client_key '{client_key}' and class_code '{class_code}' not found"
)
return {
"client_key": class_obj.client_key,
"class_code": class_obj.class_code,
"fraction": class_obj.fraction,
"us_fraction": class_obj.us_fraction,
"iva_exempt_fraction": class_obj.iva_exempt_fraction,
"sub_key": class_obj.sub_key,
"physical_review": class_obj.physical_review
}