Mindestbestände: Gruppen in Packungen erfassen (Gruppen-Gebinde)
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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() {
|
||||
</select>
|
||||
);
|
||||
}
|
||||
// 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 (
|
||||
<select value={grpPkgMode(g) ? "pkg" : "unit"} style={{ marginTop: 0, minWidth: 130 }}
|
||||
title="Einheit für den Mindestbestand umschalten"
|
||||
onChange={(e) => {
|
||||
const v = e.target.value;
|
||||
if (v === "unit") toggleGroupPkg(g, false);
|
||||
else if (v === "pkg") toggleGroupPkg(g, true);
|
||||
else openGebinde(g);
|
||||
}}>
|
||||
<option value="unit">{g.min_stock_unit_name || "Einheit"}</option>
|
||||
{g.package_size > 0 && <option value="pkg">{g.package_label || "Packung"}</option>}
|
||||
<option value="edit">{g.package_size > 0 ? "Gebinde ändern…" : "Gebinde festlegen…"}</option>
|
||||
</select>
|
||||
);
|
||||
}
|
||||
return <span className="muted">{r.unit || "–"}</span>;
|
||||
} },
|
||||
{ key: "bestand", header: "Bestand", width: 120, align: "num",
|
||||
@@ -446,6 +513,32 @@ export default function MinStock() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isAdmin && gebindeDlg && (
|
||||
<div className="card" style={{ marginBottom: "var(--sp-3)" }}>
|
||||
<div className="card-head"><Icon name="package" /><h2>Gruppen-Gebinde festlegen</h2></div>
|
||||
<p className="muted small" style={{ marginTop: 0 }}>
|
||||
Wie viel zählt „1 Packung" dieser Gruppe? Ein Richtwert – die Produkte
|
||||
der Gruppe dürfen unterschiedlich große Packungen haben.
|
||||
</p>
|
||||
<div className="row">
|
||||
<label style={{ maxWidth: 200 }}>
|
||||
Bezeichnung
|
||||
<input value={gebindeDlg.label} placeholder="z.B. Glas"
|
||||
onChange={(e) => setGebindeDlg({ ...gebindeDlg, label: e.target.value })} />
|
||||
</label>
|
||||
<label style={{ maxWidth: 220 }}>
|
||||
Größe{gebindeDlg.baseShort ? ` (in ${gebindeDlg.baseShort})` : ""}
|
||||
<input type="number" step="any" min="0" value={gebindeDlg.size} placeholder="z.B. 150"
|
||||
onChange={(e) => setGebindeDlg({ ...gebindeDlg, size: e.target.value })} />
|
||||
</label>
|
||||
</div>
|
||||
<div className="field-inline" style={{ marginTop: "var(--sp-2)" }}>
|
||||
<button className="btn primary" onClick={saveGebinde}>Speichern</button>
|
||||
<button className="btn" onClick={() => setGebindeDlg(null)}>Abbrechen</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="card">
|
||||
<DataTable id="min-stock" columns={columns} rows={rows} loading={loading}
|
||||
getRowKey={(r) => r.key} empty="Noch keine Mindestbestände." />
|
||||
|
||||
Reference in New Issue
Block a user