""" API Endpoints for Items management Handles CRUD operations for Item with one-to-many relationships to LineItems """ from typing import Dict, Any, Optional from fastapi import APIRouter, Depends, HTTPException, Query, Path, status from sqlalchemy.orm import Session from core.database import get_core_db from core.security import get_current_user, validate_access_to_resource from .schemas import ( ItemCreate, ItemUpdate, ItemResponse, ItemListResponse, ) from .service import ItemService router = APIRouter(prefix="/items", tags=["Items"]) # ============================================================================ # ITEM CRUD ENDPOINTS # ============================================================================ @router.post("/", response_model=ItemResponse, status_code=status.HTTP_201_CREATED) async def create_item( item_data: ItemCreate, company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), current_user: Dict[str, Any] = Depends(get_current_user), ): """ Create a new item with multiple line items and their nested data The item follows a one-to-many relationship structure: - Item has many LineItems - Each LineItem has one LineFinancial - Each LineItem has one LineQuantity - Each LineItem has one LineCustoms - Each LineItem has one LineDescription - Each LineItem has one LineReference """ tenant_id = validate_access_to_resource(db, company_id, current_user) service = ItemService() return service.create(db, item_data, tenant_id, company_id) @router.get("/{item_id}", response_model=ItemResponse) async def get_item( item_id: int = Path(..., description="Item ID"), company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), current_user: Dict[str, Any] = Depends(get_current_user), ): """ Get a specific item by ID with all nested data """ tenant_id = validate_access_to_resource(db, company_id, current_user) service = ItemService() item = service.get_by_id(db, item_id, tenant_id, company_id) if not item: raise HTTPException(status_code=404, detail="Item not found") return item @router.get("/", response_model=ItemListResponse) async def list_items( company_id: int = Query(..., description="Company ID"), skip: int = Query(0, ge=0, description="Number of records to skip"), limit: int = Query(100, ge=1, le=1000, description="Maximum records to return"), invoice_id: Optional[int] = Query( None, description="Filter by invoice ID"), item_type: Optional[str] = Query(None, description="Filter by item type"), system_origin: Optional[str] = Query( None, description="Filter by system origin (SCAF/SCAII)"), search: Optional[str] = Query( None, description="Search term for invoice number, reference, order, or guide"), db: Session = Depends(get_core_db), current_user: Dict[str, Any] = Depends(get_current_user), ): """ List items with optional filtering and pagination Filters: - invoice_id: Filter by specific invoice - item_type: Filter by item type (IMPORT_TEMP, IMPORT_DEF, EXPORT, REPAIR) - system_origin: Filter by system (SCAF, SCAII) - search: Search across multiple fields """ tenant_id = validate_access_to_resource(db, company_id, current_user) service = ItemService() filters = { "invoice_id": invoice_id, "item_type": item_type, "system_origin": system_origin, "search": search, } items, total = service.get_all( db, tenant_id, company_id, skip, limit, filters) return ItemListResponse( total=total, items=items, skip=skip, limit=limit ) @router.put("/{item_id}", response_model=ItemResponse) async def update_item( item_id: int = Path(..., description="Item ID"), item_data: ItemUpdate = ..., company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), current_user: Dict[str, Any] = Depends(get_current_user), ): """ Update an item and optionally its nested line data """ tenant_id = validate_access_to_resource(db, company_id, current_user) # Verify the item exists and belongs to the tenant/company service = ItemService() existing_item = service.get_by_id(db, item_id, tenant_id, company_id) if not existing_item: raise HTTPException(status_code=404, detail="Item not found") return service.update(db, item_id, item_data, tenant_id, company_id) @router.delete("/{item_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_item( item_id: int = Path(..., description="Item ID"), company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), current_user: Dict[str, Any] = Depends(get_current_user), ): """ Delete an item and all its related data (cascade delete) """ tenant_id = validate_access_to_resource(db, company_id, current_user) service = ItemService() # Verify the item exists and belongs to the tenant/company existing_item = service.get_by_id(db, item_id, tenant_id, company_id) if not existing_item: raise HTTPException(status_code=404, detail="Item not found") success = service.delete(db, item_id, tenant_id, company_id) if not success: raise HTTPException(status_code=404, detail="Item not found") return None # ============================================================================ # ADDITIONAL ENDPOINTS FOR INVOICE # ============================================================================ @router.get("/invoice/{invoice_id}/items", response_model=ItemListResponse) async def get_items_by_invoice( invoice_id: int = Path(..., description="Invoice ID"), company_id: int = Query(..., description="Company ID"), skip: int = Query(0, ge=0), limit: int = Query(100, ge=1, le=1000), db: Session = Depends(get_core_db), current_user: Dict[str, Any] = Depends(get_current_user), ): """ Get all items for a specific invoice """ tenant_id = validate_access_to_resource(db, company_id, current_user) service = ItemService() items, total = service.get_by_invoice( db, invoice_id, tenant_id, company_id, skip, limit) return ItemListResponse( total=total, items=items, skip=skip, limit=limit ) # STATISTICS & UTILITIES # ============================================================================ @router.get("/stats/summary") async def get_items_summary( company_id: int = Query(..., description="Company ID"), invoice_id: Optional[int] = Query( None, description="Filter by invoice ID"), db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user), ): """ Get summary statistics for items """ tenant_id = validate_access_to_resource(db, company_id, current_user) filters = {"invoice_id": invoice_id} if invoice_id else None items, total = ItemService.get_all( db=db, tenant_id=tenant_id, company_id=company_id, skip=0, limit=10000, # Get all for stats filters=filters ) # Calculate stats stats = { "total_items": total, "by_type": {}, "by_system": {}, } for item in items: # Count by type if item.item_type: stats["by_type"][item.item_type] = stats["by_type"].get( item.item_type, 0) + 1 # Count by system if item.system_origin: stats["by_system"][item.system_origin] = stats["by_system"].get( item.system_origin, 0) + 1 return stats