Olympus/backend/hermes/app/api/inventory.py
2026-07-05 11:54:23 +02:00

319 lines
11 KiB
Python

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