✨ 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
262 lines
8.0 KiB
Python
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
|
|
}
|
|
|
|
|