From 86e0edace825469a487ea024901b233dae677342 Mon Sep 17 00:00:00 2001 From: Scarriffle Date: Fri, 7 Aug 2026 18:42:15 +0200 Subject: [PATCH] =?UTF-8?q?Mindestbest=C3=A4nde:=20Gruppen=20in=20Packunge?= =?UTF-8?q?n=20erfassen=20(Gruppen-Gebinde)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gruppen mit verwalteter Einheit lassen sich in der Spalte 'Einheit' auf ein Gruppen-Gebinde umschalten. Über 'Gebinde festlegen…' definiert man einmalig '1 Packung ≈ X g/ml' (Richtwert, da Produkte unterschiedlich große Packungen haben können). Danach werden Mindestbestand UND Bestand der Gruppe in Packungen angezeigt/erfasst; das Backend rechnet vorhandene Werte beim Umschalten um. Co-Authored-By: Claude Opus 4.8 --- web/src/pages/MinStock.jsx | 103 +++++++++++++++++++++++++++++++++++-- 1 file changed, 98 insertions(+), 5 deletions(-) diff --git a/web/src/pages/MinStock.jsx b/web/src/pages/MinStock.jsx index 308f1d6..9cfd487 100644 --- a/web/src/pages/MinStock.jsx +++ b/web/src/pages/MinStock.jsx @@ -30,6 +30,15 @@ const effFactor = (p) => (pkgMode(p) ? p.package_size : dispFactor(p)); // Einheitslabel der gewählten Erfassungseinheit, passend zur Menge (1 Glas / 3 Gläser). const effUnit = (p, menge = 1) => (pkgMode(p) ? gebinde(menge, p.package_label || "Packung") : dispLabel(p)); +// Gruppen: analog, mit dem GRUPPEN-Gebinde (Richtwert der Gruppe, nicht der +// einzelnen Produkte). Packungen nur möglich, wenn die Gruppen-Einheit (Art) +// bekannt ist. +const grpPkgMode = (g) => !!g.min_stock_in_packages && !!(g.package_size && g.package_size > 0); +const grpUnit = (g, menge = 1) => (grpPkgMode(g) + ? gebinde(menge, g.package_label || "Packung") + : (g.min_stock_unit_name || "")); +const kindShort = (kind) => ({ weight: "g", volume: "ml", count: "Stück" }[kind] || ""); + /** * Zentrale Liste aller Mindestbestände: je Produkt/Gruppe der Gesamt-Wert und die * Bedarfe je Lagerort, inline editierbar. Ergänzt die einzelnen Produkt-/Gruppen- @@ -49,6 +58,8 @@ export default function MinStock() { // „+ hinzufügen"-Dialog: Ziel (Produkt/Gruppe) → Geltung (Gesamt/Lagerort) → Menge. const [showAdd, setShowAdd] = useState(false); const [draft, setDraft] = useState(EMPTY_DRAFT); + // Gruppen-Gebinde festlegen/ändern: { id, size, label, baseShort }. + const [gebindeDlg, setGebindeDlg] = useState(null); async function load() { setLoading(true); @@ -94,16 +105,18 @@ export default function MinStock() { out.push({ key: `g:${g.id}:global`, kind: "group", scope: "global", entity: g, name: g.name, catId: null, ort: "(Gesamt)", - soll: g.min_stock, unit: g.min_stock_unit_name || "", bestand: g.stock, bestandUnit: g.min_stock_unit_name || "", + // soll/bestand kommen vom Server schon in der aktuellen Einheit (Gebinde + // ODER verwaltete Einheit) – hier nur das passende Label. + soll: g.min_stock, unit: grpUnit(g, g.min_stock ?? 1), + bestand: g.stock, bestandUnit: grpUnit(g, g.stock ?? 1), }); for (const l of (g.location_min_stocks || [])) { out.push({ 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) || "?", - // 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 || "", + soll: l.min_stock, unit: grpUnit(g, l.min_stock ?? 1), + bestand: l.stock ?? null, bestandUnit: grpUnit(g, l.stock ?? 1), }); } } @@ -161,6 +174,42 @@ export default function MinStock() { } catch (err) { setError(err.message); } } + // Gruppen-Einheit umschalten: verwaltete Einheit ↔ Gruppen-Gebinde. Die Werte + // rechnet das Backend um (physischer Bedarf bleibt gleich). Ohne definiertes + // Gebinde erst den Festlegen-Dialog öffnen. + async function toggleGroupPkg(g, wantPkg) { + if (wantPkg === grpPkgMode(g)) return; + if (wantPkg && !(g.package_size > 0)) { openGebinde(g); return; } + setError(null); + try { + await api.updateGroup(g.id, { min_stock_in_packages: wantPkg }); + await load(); + } catch (err) { setError(err.message); } + } + + function openGebinde(g) { + setGebindeDlg({ + id: g.id, size: g.package_size ?? "", label: g.package_label ?? "", + baseShort: kindShort(g.kind), + }); + } + + async function saveGebinde() { + if (!gebindeDlg) return; + const size = Number(String(gebindeDlg.size).trim().replace(",", ".")); + if (!size || size <= 0) { setError("Bitte eine Gebinde-Größe größer 0 angeben."); return; } + setError(null); + try { + await api.updateGroup(gebindeDlg.id, { + package_size: size, + package_label: (gebindeDlg.label || "Packung").trim(), + min_stock_in_packages: true, + }); + setGebindeDlg(null); + await load(); + } catch (err) { setError(err.message); } + } + // Mindestbestand ganz entfernen: Gesamt-Wert auf NULL, je-Lagerort-Eintrag raus. // Das Feld leeren tut dasselbe – aber ein eigener Knopf macht das Löschen // eindeutig (und bei Gesamt-Werten sichtbar, da die Zeile sonst bestehen bleibt). @@ -246,7 +295,7 @@ export default function MinStock() { const draftUsePkg = draft.kind === "product" && !!draftTarget && hasPkg(draftTarget) && draft.pkg; const draftUnit = draft.kind === "product" ? (draftTarget ? (draftUsePkg ? (draftTarget.package_label || "Packung") : dispLabel(draftTarget)) : "") - : (draftTarget ? (draftTarget.min_stock_unit_name || "Stück") : ""); + : (draftTarget ? (grpUnit(draftTarget) || "Stück") : ""); function resetAdd() { setShowAdd(false); @@ -343,6 +392,24 @@ export default function MinStock() { ); } + // Gruppen mit verwalteter Einheit: umschaltbar auf ein Gruppen-Gebinde. + if (isAdmin && r.kind === "group" && r.entity.min_stock_unit_id) { + const g = r.entity; + return ( + + ); + } return {r.unit || "–"}; } }, { key: "bestand", header: "Bestand", width: 120, align: "num", @@ -446,6 +513,32 @@ export default function MinStock() { )} + {isAdmin && gebindeDlg && ( +
+

Gruppen-Gebinde festlegen

+

+ Wie viel zählt „1 Packung" dieser Gruppe? Ein Richtwert – die Produkte + der Gruppe dürfen unterschiedlich große Packungen haben. +

+
+ + +
+
+ + +
+
+ )} +
r.key} empty="Noch keine Mindestbestände." />