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 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-07-30 13:57:40 +02:00
parent 4ed33e221f
commit f3bab15362
4 changed files with 22 additions and 4 deletions

View File

@@ -12,7 +12,7 @@ from sqlalchemy.orm import Session
from .models import Barcode, Category, CategoryTracking, Item, Lot, Product, ProductImage from .models import Barcode, Category, CategoryTracking, Item, Lot, Product, ProductImage
from .schemas import BarcodeOut, LocationMinStockOut, ProductOut from .schemas import BarcodeOut, LocationMinStockOut, ProductOut
from .services.conversion import KIND_OF_BASE, display_unit_info 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: 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_id=e.location_id,
location_name=e.location.name if e.location else None, location_name=e.location.name if e.location else None,
min_stock=e.min_stock, 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) 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_id=e.location_id,
location_name=e.location.name if e.location else None, location_name=e.location.name if e.location else None,
min_stock=e.min_stock, 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) for e in sorted(product.location_min_stocks, key=lambda x: x.id)
] ]

View File

@@ -16,7 +16,7 @@ from ..schemas import (
ProductBarcodeOut, ProductBarcodeOut,
) )
from ..services.conversion import BASE_OF_KIND 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"]) 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.min_stock_unit_name = unit.name
out.kind = unit.kind.value out.kind = unit.kind.value
else: else:
matching = list(products)
out.stock = float(sum(current_stock(db, p.id) for p in 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 = [ out.location_min_stocks = [
LocationMinStockOut( LocationMinStockOut(
location_id=e.location_id, location_id=e.location_id,
location_name=e.location.name if e.location else None, location_name=e.location.name if e.location else None,
min_stock=e.min_stock, min_stock=e.min_stock,
stock=_loc_stock(e.location_id),
) )
for e in sorted(group.location_min_stocks, key=lambda x: x.id) for e in sorted(group.location_min_stocks, key=lambda x: x.id)
] ]

View File

@@ -120,6 +120,11 @@ class LocationMinStockOut(BaseModel):
location_id: str location_id: str
location_name: str | None = None location_name: str | None = None
min_stock: float 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): class GroupOut(BaseModel):

View File

@@ -73,7 +73,9 @@ export default function MinStock() {
locId: l.location_id, name: p.name, catId: p.category_id, locId: l.location_id, name: p.name, catId: p.category_id,
ort: locationPathById(l.location_id, locations) || "?", ort: locationPathById(l.location_id, locations) || "?",
// je-Lagerort ist in Artikeleinheiten gespeichert → in Anzeigeeinheit umrechnen. // 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, key: `g:${g.id}:${l.location_id}`, kind: "group", scope: "loc", entity: g,
locId: l.location_id, name: g.name, catId: null, locId: l.location_id, name: g.name, catId: null,
ort: locationPathById(l.location_id, locations) || "?", 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 || "",
}); });
} }
} }