from fastapi import APIRouter, Depends, HTTPException, Query, status from sqlalchemy.orm import Session from starlette.requests import Request from app.core.rbac import require_permission from app.db.database import get_db from app.models.inventory import InventoryCategory, InventoryItem, InventoryLocation, InventorySupplier from app.models.user import User from app.repositories.inventory_repository import InventoryRepository from app.schemas.inventory import ( InventoryCategoryPayload, InventoryCategoryResponse, InventoryItemCreate, InventoryItemListResponse, InventoryItemResponse, InventoryItemUpdate, InventoryLocationPayload, InventoryLocationResponse, InventoryStockAction, InventoryStockMovementResponse, InventorySupplierPayload, InventorySupplierResponse, ) from app.services.inventory_service import InventoryService router = APIRouter(prefix="/inventory", tags=["Inventory"]) def get_item_or_404(db: Session, item_id: int) -> InventoryItem: item = InventoryRepository.get_item(db, item_id) if item is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Artikel nicht gefunden") return item def get_category_or_404(db: Session, category_id: int) -> InventoryCategory: category = InventoryRepository.get_category(db, category_id) if category is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Kategorie nicht gefunden") return category def get_location_or_404(db: Session, location_id: int) -> InventoryLocation: location = InventoryRepository.get_location(db, location_id) if location is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Lagerort nicht gefunden") return location def get_supplier_or_404(db: Session, supplier_id: int) -> InventorySupplier: supplier = InventoryRepository.get_supplier(db, supplier_id) if supplier is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Lieferant nicht gefunden") return supplier @router.get("/items", response_model=InventoryItemListResponse) def list_items( q: str | None = None, category_id: int | None = None, supplier_id: int | None = None, location_id: int | None = None, low_stock: bool | None = None, active: bool | None = True, limit: int = Query(default=50, ge=1, le=200), offset: int = Query(default=0, ge=0), db: Session = Depends(get_db), current_user: User = Depends(require_permission("inventory.read")), ): items, total = InventoryRepository.list_items( db, q=q, category_id=category_id, supplier_id=supplier_id, location_id=location_id, low_stock=low_stock, active=active, limit=limit, offset=offset, ) return InventoryItemListResponse(items=items, total=total, limit=limit, offset=offset) @router.get("/items/search", response_model=list[InventoryItemResponse]) def search_items( q: str | None = None, category: int | None = None, manufacturer: str | None = None, limit: int = Query(default=20, ge=1, le=50), db: Session = Depends(get_db), current_user: User = Depends(require_permission("inventory.read")), ): return InventoryRepository.search_items(db, q=q, category_id=category, manufacturer=manufacturer, limit=limit) @router.post("/items", response_model=InventoryItemResponse, status_code=status.HTTP_201_CREATED) def create_item( payload: InventoryItemCreate, request: Request, db: Session = Depends(get_db), current_user: User = Depends(require_permission("inventory.create")), ): return InventoryService.create_item(db, payload, actor=current_user, request=request) @router.get("/items/{item_id}", response_model=InventoryItemResponse) def get_item( item_id: int, db: Session = Depends(get_db), current_user: User = Depends(require_permission("inventory.read")), ): return get_item_or_404(db, item_id) @router.put("/items/{item_id}", response_model=InventoryItemResponse) def update_item( item_id: int, payload: InventoryItemUpdate, request: Request, db: Session = Depends(get_db), current_user: User = Depends(require_permission("inventory.update")), ): item = get_item_or_404(db, item_id) return InventoryService.update_item(db, item, payload, actor=current_user, request=request) @router.delete("/items/{item_id}", response_model=InventoryItemResponse | None) def delete_item( item_id: int, request: Request, db: Session = Depends(get_db), current_user: User = Depends(require_permission("inventory.delete")), ): item = get_item_or_404(db, item_id) return InventoryService.delete_item(db, item, actor=current_user, request=request) @router.post("/items/{item_id}/stock/adjust", response_model=InventoryItemResponse) def adjust_stock( item_id: int, payload: InventoryStockAction, request: Request, db: Session = Depends(get_db), current_user: User = Depends(require_permission("inventory.stock.adjust")), ): item = get_item_or_404(db, item_id) return InventoryService.adjust_stock(db, item, payload, actor=current_user, request=request) @router.post("/items/{item_id}/stock/reserve", response_model=InventoryItemResponse) def reserve_stock( item_id: int, payload: InventoryStockAction, request: Request, db: Session = Depends(get_db), current_user: User = Depends(require_permission("inventory.stock.reserve")), ): item = get_item_or_404(db, item_id) return InventoryService.reserve_stock(db, item, payload, actor=current_user, request=request) @router.post("/items/{item_id}/stock/release", response_model=InventoryItemResponse) def release_stock( item_id: int, payload: InventoryStockAction, request: Request, db: Session = Depends(get_db), current_user: User = Depends(require_permission("inventory.stock.reserve")), ): item = get_item_or_404(db, item_id) return InventoryService.release_stock(db, item, payload, actor=current_user, request=request) @router.post("/items/{item_id}/stock/consume", response_model=InventoryItemResponse) def consume_stock( item_id: int, payload: InventoryStockAction, request: Request, db: Session = Depends(get_db), current_user: User = Depends(require_permission("inventory.stock.consume")), ): item = get_item_or_404(db, item_id) return InventoryService.consume_stock(db, item, payload, actor=current_user, request=request) @router.get("/items/{item_id}/movements", response_model=list[InventoryStockMovementResponse]) def list_movements( item_id: int, db: Session = Depends(get_db), current_user: User = Depends(require_permission("inventory.read")), ): get_item_or_404(db, item_id) return InventoryRepository.list_movements(db, item_id) @router.get("/categories", response_model=list[InventoryCategoryResponse]) def list_categories( db: Session = Depends(get_db), current_user: User = Depends(require_permission("inventory.read")), ): return InventoryRepository.list_categories(db) @router.post("/categories", response_model=InventoryCategoryResponse, status_code=status.HTTP_201_CREATED) def create_category( payload: InventoryCategoryPayload, request: Request, db: Session = Depends(get_db), current_user: User = Depends(require_permission("inventory.manage.masterdata")), ): return InventoryService.create_category(db, payload, actor=current_user, request=request) @router.put("/categories/{category_id}", response_model=InventoryCategoryResponse) def update_category( category_id: int, payload: InventoryCategoryPayload, request: Request, db: Session = Depends(get_db), current_user: User = Depends(require_permission("inventory.manage.masterdata")), ): category = get_category_or_404(db, category_id) return InventoryService.update_category(db, category, payload, actor=current_user, request=request) @router.delete("/categories/{category_id}", status_code=status.HTTP_204_NO_CONTENT) def delete_category( category_id: int, request: Request, db: Session = Depends(get_db), current_user: User = Depends(require_permission("inventory.manage.masterdata")), ): category = get_category_or_404(db, category_id) InventoryService.delete_master(db, category, action="inventory.categories.delete", actor=current_user, request=request) return None @router.get("/locations", response_model=list[InventoryLocationResponse]) def list_locations( db: Session = Depends(get_db), current_user: User = Depends(require_permission("inventory.read")), ): return InventoryRepository.list_locations(db) @router.post("/locations", response_model=InventoryLocationResponse, status_code=status.HTTP_201_CREATED) def create_location( payload: InventoryLocationPayload, request: Request, db: Session = Depends(get_db), current_user: User = Depends(require_permission("inventory.manage.masterdata")), ): return InventoryService.create_location(db, payload, actor=current_user, request=request) @router.put("/locations/{location_id}", response_model=InventoryLocationResponse) def update_location( location_id: int, payload: InventoryLocationPayload, request: Request, db: Session = Depends(get_db), current_user: User = Depends(require_permission("inventory.manage.masterdata")), ): location = get_location_or_404(db, location_id) return InventoryService.update_location(db, location, payload, actor=current_user, request=request) @router.delete("/locations/{location_id}", status_code=status.HTTP_204_NO_CONTENT) def delete_location( location_id: int, request: Request, db: Session = Depends(get_db), current_user: User = Depends(require_permission("inventory.manage.masterdata")), ): location = get_location_or_404(db, location_id) InventoryService.delete_master(db, location, action="inventory.locations.delete", actor=current_user, request=request) return None @router.get("/suppliers", response_model=list[InventorySupplierResponse]) def list_suppliers( db: Session = Depends(get_db), current_user: User = Depends(require_permission("inventory.read")), ): return InventoryRepository.list_suppliers(db) @router.post("/suppliers", response_model=InventorySupplierResponse, status_code=status.HTTP_201_CREATED) def create_supplier( payload: InventorySupplierPayload, request: Request, db: Session = Depends(get_db), current_user: User = Depends(require_permission("inventory.manage.masterdata")), ): return InventoryService.create_supplier(db, payload, actor=current_user, request=request) @router.put("/suppliers/{supplier_id}", response_model=InventorySupplierResponse) def update_supplier( supplier_id: int, payload: InventorySupplierPayload, request: Request, db: Session = Depends(get_db), current_user: User = Depends(require_permission("inventory.manage.masterdata")), ): supplier = get_supplier_or_404(db, supplier_id) return InventoryService.update_supplier(db, supplier, payload, actor=current_user, request=request) @router.delete("/suppliers/{supplier_id}", status_code=status.HTTP_204_NO_CONTENT) def delete_supplier( supplier_id: int, request: Request, db: Session = Depends(get_db), current_user: User = Depends(require_permission("inventory.manage.masterdata")), ): supplier = get_supplier_or_404(db, supplier_id) InventoryService.delete_master(db, supplier, action="inventory.suppliers.delete", actor=current_user, request=request) return None