Web: Mindestbestaende in echter Einheit statt Packung

"1 Packung" ist nichtssagend (kann 500 g, 1 kg oder 25 ml sein). Die Seite zeigt
und bearbeitet Produkt-Mindestbestaende jetzt in der Anzeigeeinheit des Artikels
(Gramm/Milliliter/Stueck): global (Basiseinheiten) und je Lagerort (Artikel-
einheiten) werden dafuer umgerechnet. Rigatoni zeigt jetzt z.B. "500 Gramm".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-07-28 08:48:32 +02:00
parent f0c64466e2
commit 275611160b

View File

@@ -8,14 +8,15 @@ import { categoryInfoMap } from "../categoryPath";
import { locationPathById } from "../locationPath"; import { locationPathById } from "../locationPath";
import { fmt } from "../units"; import { fmt } from "../units";
// Artikeleinheit eines Produkts (Gebinde oder Produkteinheit) wie in der Produktliste. // Anzeigeeinheit eines Produkts: die im Formular gewählte Einheit (Gramm,
// Wichtig: Zahl (articleUnit) und Bezeichnung (articleLabel) müssen zusammenpassen // Milliliter, Stück) NICHT die Packung. „1 Packung" ist nichtssagend, weil eine
// bei vorhandener Packungsgröße also „Packung" und NICHT die Basiseinheit, sonst steht // Packung 500 g, 1 kg oder 25 ml sein kann; deshalb rechnen wir alles in die
// z.B. „1 Gramm" statt „1 Packung" (500 g) da. // echte Einheit um. dispFactor = Basiseinheiten je Anzeigeeinheit.
const dispFactor = (p) => (p.unit_factor && p.unit_factor > 0 ? p.unit_factor : 1);
const dispLabel = (p) => (p.unit_name || "Stück");
// Artikeleinheit (Packung, sonst Anzeigeeinheit): nur noch nötig, um die je-Lagerort-
// Mindestbestände umzurechnen, die in Artikeleinheiten gespeichert sind.
const articleUnit = (p) => (p.package_size && p.package_size > 0 ? p.package_size : (p.unit_factor || 1)); const articleUnit = (p) => (p.package_size && p.package_size > 0 ? p.package_size : (p.unit_factor || 1));
const articleLabel = (p) => (
p.package_size && p.package_size > 0 ? (p.package_label || "Packung") : (p.unit_name || "Stück")
);
/** /**
* Zentrale Liste aller Mindestbestände: je Produkt/Gruppe der Gesamt-Wert und die * Zentrale Liste aller Mindestbestände: je Produkt/Gruppe der Gesamt-Wert und die
@@ -53,15 +54,19 @@ export default function MinStock() {
out.push({ out.push({
key: `p:${p.id}:global`, kind: "product", scope: "global", entity: p, key: `p:${p.id}:global`, kind: "product", scope: "global", entity: p,
name: p.name, catId: p.category_id, ort: "(Gesamt)", name: p.name, catId: p.category_id, ort: "(Gesamt)",
soll: p.min_stock_display, unit: p.min_stock_unit_label || articleLabel(p), // Alles in der Anzeigeeinheit (Gramm/Milliliter/Stück): min_stock ist in
bestand: p.stock / articleUnit(p), bestandUnit: articleLabel(p), // Basiseinheiten gespeichert.
soll: p.min_stock != null ? p.min_stock / dispFactor(p) : null,
unit: dispLabel(p),
bestand: p.stock / dispFactor(p), bestandUnit: dispLabel(p),
}); });
for (const l of (p.location_min_stocks || [])) { for (const l of (p.location_min_stocks || [])) {
out.push({ out.push({
key: `p:${p.id}:${l.location_id}`, kind: "product", scope: "loc", entity: p, key: `p:${p.id}:${l.location_id}`, kind: "product", scope: "loc", entity: p,
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) || "?",
soll: l.min_stock, unit: articleLabel(p), bestand: null, // je-Lagerort ist in Artikeleinheiten gespeichert → in Anzeigeeinheit umrechnen.
soll: (l.min_stock * articleUnit(p)) / dispFactor(p), unit: dispLabel(p), bestand: null,
}); });
} }
} }
@@ -96,20 +101,17 @@ export default function MinStock() {
try { try {
if (row.kind === "product" && row.scope === "global") { if (row.kind === "product" && row.scope === "global") {
const p = row.entity; const p = row.entity;
// Faktor aus dem bestehenden Wert ableiten (erhält die erfasste Einheit); // Eingabe in der Anzeigeeinheit → Basiseinheiten speichern.
// ohne bestehenden Wert in Artikeleinheiten setzen.
if (p.min_stock != null && p.min_stock_display) {
const faktor = p.min_stock / p.min_stock_display;
await api.updateProduct(p.id, { min_stock: num == null ? null : Math.round(num * faktor) });
} else {
await api.updateProduct(p.id, { await api.updateProduct(p.id, {
min_stock: num == null ? null : Math.round(num * articleUnit(p)), min_stock: num == null ? null : Math.round(num * dispFactor(p)),
min_stock_in_packages: (p.package_size || 0) > 0, min_stock_in_packages: false,
min_stock_unit_id: null, min_stock_unit_id: p.display_unit_id ?? null,
}); });
}
} else if (row.kind === "product" && row.scope === "loc") { } else if (row.kind === "product" && row.scope === "loc") {
await api.setProductLocationMinStock(row.entity.id, mergeLoc(row.entity.location_min_stocks, row.locId, num)); const p = row.entity;
// Eingabe in der Anzeigeeinheit → Artikeleinheiten (so gespeichert).
const artikel = num == null ? null : (num * dispFactor(p)) / articleUnit(p);
await api.setProductLocationMinStock(p.id, mergeLoc(p.location_min_stocks, row.locId, artikel));
} else if (row.kind === "group" && row.scope === "global") { } else if (row.kind === "group" && row.scope === "global") {
await api.updateGroup(row.entity.id, { min_stock: num }); await api.updateGroup(row.entity.id, { min_stock: num });
} else if (row.kind === "group" && row.scope === "loc") { } else if (row.kind === "group" && row.scope === "loc") {
@@ -124,7 +126,7 @@ export default function MinStock() {
? products.find((p) => String(p.id) === String(draft.targetId)) ? products.find((p) => String(p.id) === String(draft.targetId))
: groups.find((g) => String(g.id) === String(draft.targetId)); : groups.find((g) => String(g.id) === String(draft.targetId));
const draftUnit = draft.kind === "product" const draftUnit = draft.kind === "product"
? (draftTarget ? articleLabel(draftTarget) : "") ? (draftTarget ? dispLabel(draftTarget) : "")
: (draftTarget ? (draftTarget.min_stock_unit_name || "Stück") : ""); : (draftTarget ? (draftTarget.min_stock_unit_name || "Stück") : "");
function resetAdd() { function resetAdd() {
@@ -143,15 +145,16 @@ export default function MinStock() {
if (draft.kind === "product") { if (draft.kind === "product") {
const p = draftTarget; const p = draftTarget;
if (draft.scope === "global") { if (draft.scope === "global") {
// Menge in der Artikeleinheit erfasst → in Basiseinheiten speichern, // Menge in der Anzeigeeinheit erfasst → in Basiseinheiten speichern.
// wie beim Inline-Editieren ohne bestehenden Wert.
await api.updateProduct(p.id, { await api.updateProduct(p.id, {
min_stock: Math.round(num * articleUnit(p)), min_stock: Math.round(num * dispFactor(p)),
min_stock_in_packages: (p.package_size || 0) > 0, min_stock_in_packages: false,
min_stock_unit_id: null, min_stock_unit_id: p.display_unit_id ?? null,
}); });
} else { } else {
await api.setProductLocationMinStock(p.id, mergeLoc(p.location_min_stocks, draft.locId, num)); // Anzeigeeinheit → Artikeleinheiten (so gespeichert).
const artikel = (num * dispFactor(p)) / articleUnit(p);
await api.setProductLocationMinStock(p.id, mergeLoc(p.location_min_stocks, draft.locId, artikel));
} }
} else { } else {
const g = draftTarget; const g = draftTarget;