Backend (FastAPI + PostgreSQL): Chargen mit MHD, FEFO-Auslagern, Einheiten-Umrechnung (Stueck/g/ml + Packungen), Open-Food-Facts-Lookup mit lokalem Fallback, JWT-Auth mit Rollen (Admin/Nutzer), erster Admin beim Setup, Einkaufsliste, Ablaufwarnung, Lagerorte, pytest fuer FEFO. Web-UI (React/Vite): Login, Dashboard, Ein-/Auslagern, Produkte, Lagerorte, Benutzerverwaltung, Einkaufsliste - rollenabhaengig. Deploy: docker-compose + install.sh (Docker-Autoinstall, Secrets), README und Roadmap fuer Schritt 2 (iOS) und Schritt 3. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
84 lines
2.5 KiB
Python
84 lines
2.5 KiB
Python
from fastapi import APIRouter, Depends, HTTPException, status
|
|
from sqlalchemy.orm import Session
|
|
|
|
from ..crud import resolve_product
|
|
from ..database import get_db
|
|
from ..deps import get_current_user
|
|
from ..models import Lot, User
|
|
from ..schemas import (
|
|
CheckInRequest,
|
|
CheckInResponse,
|
|
CheckOutRequest,
|
|
CheckOutResponse,
|
|
LotOut,
|
|
)
|
|
from ..services.stock import StockError, check_in, check_out, current_stock
|
|
from ..services.units import UnitError
|
|
|
|
router = APIRouter(tags=["stock"])
|
|
|
|
|
|
@router.post("/stock/checkin", response_model=CheckInResponse)
|
|
def stock_checkin(
|
|
payload: CheckInRequest,
|
|
db: Session = Depends(get_db),
|
|
user: User = Depends(get_current_user),
|
|
) -> CheckInResponse:
|
|
product = resolve_product(db, payload.product_id, payload.barcode)
|
|
try:
|
|
lot = check_in(
|
|
db,
|
|
product=product,
|
|
quantity=payload.quantity,
|
|
unit=payload.unit,
|
|
best_before=payload.best_before,
|
|
location_id=payload.location_id,
|
|
user=user,
|
|
note=payload.note,
|
|
)
|
|
except UnitError as exc:
|
|
raise HTTPException(status.HTTP_400_BAD_REQUEST, str(exc)) from exc
|
|
db.commit()
|
|
db.refresh(lot)
|
|
return CheckInResponse(
|
|
lot=LotOut.model_validate(lot), product_stock=current_stock(db, product.id)
|
|
)
|
|
|
|
|
|
@router.post("/stock/checkout", response_model=CheckOutResponse)
|
|
def stock_checkout(
|
|
payload: CheckOutRequest,
|
|
db: Session = Depends(get_db),
|
|
user: User = Depends(get_current_user),
|
|
) -> CheckOutResponse:
|
|
product = resolve_product(db, payload.product_id, payload.barcode)
|
|
try:
|
|
affected = check_out(
|
|
db,
|
|
product=product,
|
|
quantity=payload.quantity,
|
|
unit=payload.unit,
|
|
user=user,
|
|
note=payload.note,
|
|
)
|
|
except UnitError as exc:
|
|
raise HTTPException(status.HTTP_400_BAD_REQUEST, str(exc)) from exc
|
|
except StockError as exc:
|
|
raise HTTPException(status.HTTP_409_CONFLICT, str(exc)) from exc
|
|
db.commit()
|
|
return CheckOutResponse(
|
|
affected_lots=affected, product_stock=current_stock(db, product.id)
|
|
)
|
|
|
|
|
|
@router.get("/lots", response_model=list[LotOut])
|
|
def list_lots(
|
|
product_id: int | None = None,
|
|
db: Session = Depends(get_db),
|
|
_: User = Depends(get_current_user),
|
|
) -> list[Lot]:
|
|
query = db.query(Lot)
|
|
if product_id is not None:
|
|
query = query.filter(Lot.product_id == product_id)
|
|
return query.order_by(Lot.best_before.is_(None), Lot.best_before).all()
|