""" 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.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_id: 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 """ # Validate access to the tenant and company tenant_id = current_user.get("tenant_id") company_id = current_user.get("company_id") if not tenant_id or not company_id: raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") service = ClassService(db) search_params = ClassSearchDTO( client_id=client_id, 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_id}", response_model=List[ClassBasicDTO]) async def get_classes_by_client( client_id: 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 """ # Validate access to the tenant and company tenant_id = current_user.get("tenant_id") company_id = current_user.get("company_id") if not tenant_id or not company_id: raise HTTPException(status_code=403, detail="Access denied: Tenant or Company not found") service = ClassService(db) return service.search_by_client(client_id, 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_id}/{class_code}", response_model=ClassResponseDTO) async def get_class( client_id: int, class_code: str, db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user), ): """ Get class by composite key (client_id + class_code) """ service = ClassService(db) class_obj = service.get_class(client_id, class_code) if not class_obj: raise HTTPException( status_code=404, detail=f"Class with client_id '{client_id}' and class_code '{class_code}' not found", ) return class_obj @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.put("/{client_id}/{class_code}", response_model=ClassResponseDTO) async def update_class( client_id: 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_id, class_code, class_data) if not class_obj: raise HTTPException( status_code=404, detail=f"Class with client_id '{client_id}' and class_code '{class_code}' not found", ) return class_obj @router.delete("/{client_id}/{class_code}", status_code=status.HTTP_204_NO_CONTENT) async def delete_class( client_id: 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_id, class_code): raise HTTPException( status_code=404, detail=f"Class with client_id '{client_id}' and class_code '{class_code}' not found", ) # Endpoints específicos para información detallada @router.get("/{client_id}/{class_code}/basic", response_model=ClassBasicDTO) async def get_class_basic_info( client_id: 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_id, class_code) if not class_obj: raise HTTPException( status_code=404, detail=f"Class with client_id '{client_id}' and class_code '{class_code}' not found", ) return ClassBasicDTO( client_id=class_obj.client_id, 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_id}/{class_code}/tariff", response_model=dict) async def get_class_tariff_info( client_id: 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_id, class_code) if not class_obj: raise HTTPException( status_code=404, detail=f"Class with client_id '{client_id}' and class_code '{class_code}' not found", ) return { "client_id": class_obj.client_id, "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, }