From f3bab15362397b2e702df32019891f8cb75b4492 Mon Sep 17 00:00:00 2001 From: Scarriffle Date: Thu, 30 Jul 2026 13:57:40 +0200 Subject: [PATCH] Mindestbestaende: Bestand je Lagerort anzeigen (nicht nur Gesamt) Per-Lagerort-Zeilen (Produkt und Gruppe) zeigten '-' beim Bestand, weil die Seite nur den Gesamtbestand kannte. Jetzt liefert LocationMinStockOut.stock den Bestand AN DEM Ort (inkl. Unterorte) mit - bei Produkten in Basiseinheiten, bei Gruppen in der Gruppen-Einheit (analog zu ProductOut.stock/GroupOut.stock). Berechnet ueber location_subtree_stock_base; die Weboberflaeche zeigt ihn wie beim Gesamt-Bestand. Co-Authored-By: Claude Opus 4.8 --- backend/app/crud.py | 4 +++- backend/app/routers/groups.py | 9 ++++++++- backend/app/schemas.py | 5 +++++ web/src/pages/MinStock.jsx | 8 ++++++-- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/backend/app/crud.py b/backend/app/crud.py index d9415b5..9f8e201 100644 --- a/backend/app/crud.py +++ b/backend/app/crud.py @@ -12,7 +12,7 @@ from sqlalchemy.orm import Session from .models import Barcode, Category, CategoryTracking, Item, Lot, Product, ProductImage from .schemas import BarcodeOut, LocationMinStockOut, ProductOut from .services.conversion import KIND_OF_BASE, display_unit_info -from .services.stock import current_stock +from .services.stock import current_stock, location_subtree_stock_base def product_tracking(db: Session, product: Product) -> str: @@ -85,6 +85,7 @@ def product_to_out(db: Session, product: Product) -> ProductOut: location_id=e.location_id, location_name=e.location.name if e.location else None, min_stock=e.min_stock, + stock=location_subtree_stock_base(db, product, e.location_id), ) for e in sorted(product.location_min_stocks, key=lambda x: x.id) ] @@ -165,6 +166,7 @@ def products_to_out_bulk(db: Session, products: list[Product]) -> list[ProductOu location_id=e.location_id, location_name=e.location.name if e.location else None, min_stock=e.min_stock, + stock=location_subtree_stock_base(db, product, e.location_id), ) for e in sorted(product.location_min_stocks, key=lambda x: x.id) ] diff --git a/backend/app/routers/groups.py b/backend/app/routers/groups.py index 4795226..c908b44 100644 --- a/backend/app/routers/groups.py +++ b/backend/app/routers/groups.py @@ -16,7 +16,7 @@ from ..schemas import ( ProductBarcodeOut, ) from ..services.conversion import BASE_OF_KIND -from ..services.stock import current_stock +from ..services.stock import current_stock, location_subtree_stock_base router = APIRouter(prefix="/groups", tags=["groups"]) @@ -77,13 +77,20 @@ def _group_to_out(db: Session, group: Group) -> GroupOut: out.min_stock_unit_name = unit.name out.kind = unit.kind.value else: + matching = list(products) out.stock = float(sum(current_stock(db, p.id) for p in products)) + # Bestand je Lagerort (inkl. Unterorte) in derselben Einheit wie out.stock. + def _loc_stock(loc_id: str) -> float: + total = sum(location_subtree_stock_base(db, p, loc_id) for p in matching) + return total / unit.factor if unit is not None else float(total) + out.location_min_stocks = [ LocationMinStockOut( location_id=e.location_id, location_name=e.location.name if e.location else None, min_stock=e.min_stock, + stock=_loc_stock(e.location_id), ) for e in sorted(group.location_min_stocks, key=lambda x: x.id) ] diff --git a/backend/app/schemas.py b/backend/app/schemas.py index 94e52ff..76881c8 100644 --- a/backend/app/schemas.py +++ b/backend/app/schemas.py @@ -120,6 +120,11 @@ class LocationMinStockOut(BaseModel): location_id: str location_name: str | None = None min_stock: float + # Bestand AN DIESEM Ort (inkl. Unterorte): bei Produkten in Basiseinheiten + # (wie ``ProductOut.stock``), bei Gruppen in der Gruppen-Einheit (wie + # ``GroupOut.stock``). So kann die Oberfläche auch je Lagerort einen Bestand + # zeigen, nicht nur „Gesamt". + stock: float | None = None class GroupOut(BaseModel): diff --git a/web/src/pages/MinStock.jsx b/web/src/pages/MinStock.jsx index fc83b57..e6d26c3 100644 --- a/web/src/pages/MinStock.jsx +++ b/web/src/pages/MinStock.jsx @@ -73,7 +73,9 @@ export default function MinStock() { locId: l.location_id, name: p.name, catId: p.category_id, ort: locationPathById(l.location_id, locations) || "?", // je-Lagerort ist in Artikeleinheiten gespeichert → in Anzeigeeinheit umrechnen. - soll: (l.min_stock * articleUnit(p)) / dispFactor(p), unit: dispLabel(p), bestand: null, + soll: (l.min_stock * articleUnit(p)) / dispFactor(p), unit: dispLabel(p), + // Bestand kommt in Basiseinheiten (wie der Gesamt-Bestand) → in Anzeigeeinheit. + bestand: l.stock != null ? l.stock / dispFactor(p) : null, bestandUnit: dispLabel(p), }); } } @@ -88,7 +90,9 @@ export default function MinStock() { key: `g:${g.id}:${l.location_id}`, kind: "group", scope: "loc", entity: g, locId: l.location_id, name: g.name, catId: null, ort: locationPathById(l.location_id, locations) || "?", - soll: l.min_stock, unit: g.min_stock_unit_name || "", bestand: null, + // Gruppen-Bestand je Ort kommt schon in der Gruppen-Einheit (wie Gesamt). + soll: l.min_stock, unit: g.min_stock_unit_name || "", + bestand: l.stock ?? null, bestandUnit: g.min_stock_unit_name || "", }); } }