Einlagern-UX: 3-Wege-Barcode, Inline-Anlage aus OFF, Mehr-Chargen mit MHD

Einlagern:
- Barcode-Erkennung dreiteilig: bekannt -> direkt Menge; unbekannt aber in OFF
  -> "Anlegen & einlagern" inline; gar nicht gefunden -> manuell anlegen.
- Mehrere Chargen pro Vorgang: je Zeile eigene Menge + eigenes MHD
  (z.B. 5 Glaeser mit unterschiedlichen Daten). Neuer Endpoint /stock/checkin/batch.

Produkte/OFF:
- OFF-Fuellmenge (quantity) wird in Basiseinheit + Packungsgroesse geparst
  (kg->g, l->ml, cl/dl); parse_quantity + Tests.
- Produktformular uebernimmt Barcode aus ?barcode= und schlaegt automatisch nach,
  fuellt Packungsgroesse; gemeinsame offUtils (guessGroup/suggestionToProduct).

Lagerorte:
- Baum jetzt rekursiv ueber beliebig viele Ebenen (Schrank->Fach->Kiste->...).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-07-22 10:40:54 +02:00
parent b683f2d93b
commit 4c17705290
10 changed files with 368 additions and 105 deletions

View File

@@ -6,6 +6,8 @@ from ..database import get_db
from ..deps import get_current_user
from ..models import Lot, User
from ..schemas import (
BatchCheckInRequest,
BatchCheckInResponse,
CheckInRequest,
CheckInResponse,
CheckOutRequest,
@@ -45,6 +47,45 @@ def stock_checkin(
)
@router.post("/stock/checkin/batch", response_model=BatchCheckInResponse)
def stock_checkin_batch(
payload: BatchCheckInRequest,
db: Session = Depends(get_db),
user: User = Depends(get_current_user),
) -> BatchCheckInResponse:
"""Mehrere Chargen desselben Produkts in einem Vorgang einlagern.
Jede Zeile erzeugt eine eigene Charge mit eigener Menge und eigenem MHD
z.B. 5 Gläser mit unterschiedlichen Mindesthaltbarkeitsdaten.
"""
product = resolve_product(db, payload.product_id, payload.barcode)
lots: list[Lot] = []
try:
for line in payload.lines:
lots.append(
check_in(
db,
product=product,
quantity=line.quantity,
unit=payload.unit,
best_before=line.best_before,
location_id=line.location_id,
user=user,
note=payload.note,
)
)
except UnitError as exc:
db.rollback()
raise HTTPException(status.HTTP_400_BAD_REQUEST, str(exc)) from exc
db.commit()
for lot in lots:
db.refresh(lot)
return BatchCheckInResponse(
lots=[LotOut.model_validate(lot) for lot in lots],
product_stock=current_stock(db, product.id),
)
@router.post("/stock/checkout", response_model=CheckOutResponse)
def stock_checkout(
payload: CheckOutRequest,