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 { fmt } from "../units";
// Artikeleinheit eines Produkts (Gebinde oder Produkteinheit) wie in der Produktliste.
// Wichtig: Zahl (articleUnit) und Bezeichnung (articleLabel) müssen zusammenpassen
// bei vorhandener Packungsgröße also „Packung" und NICHT die Basiseinheit, sonst steht
// z.B. „1 Gramm" statt „1 Packung" (500 g) da.
// Anzeigeeinheit eines Produkts: die im Formular gewählte Einheit (Gramm,
// Milliliter, Stück) NICHT die Packung. „1 Packung" ist nichtssagend, weil eine
// Packung 500 g, 1 kg oder 25 ml sein kann; deshalb rechnen wir alles in die
// 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 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
@@ -53,15 +54,19 @@ export default function MinStock() {
out.push({
key: `p:${p.id}:global`, kind: "product", scope: "global", entity: p,
name: p.name, catId: p.category_id, ort: "(Gesamt)",
soll: p.min_stock_display, unit: p.min_stock_unit_label || articleLabel(p),
bestand: p.stock / articleUnit(p), bestandUnit: articleLabel(p),
// Alles in der Anzeigeeinheit (Gramm/Milliliter/Stück): min_stock ist in
// 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 || [])) {
out.push({
key: `p:${p.id}:${l.location_id}`, kind: "product", scope: "loc", entity: p,
locId: l.location_id, name: p.name, catId: p.category_id,
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 {
if (row.kind === "product" && row.scope === "global") {
const p = row.entity;
// Faktor aus dem bestehenden Wert ableiten (erhält die erfasste Einheit);
// 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 {
// Eingabe in der Anzeigeeinheit → Basiseinheiten speichern.
await api.updateProduct(p.id, {
min_stock: num == null ? null : Math.round(num * articleUnit(p)),
min_stock_in_packages: (p.package_size || 0) > 0,
min_stock_unit_id: null,
min_stock: num == null ? null : Math.round(num * dispFactor(p)),
min_stock_in_packages: false,
min_stock_unit_id: p.display_unit_id ?? null,
});
}
} 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") {
await api.updateGroup(row.entity.id, { min_stock: num });
} 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))
: groups.find((g) => String(g.id) === String(draft.targetId));
const draftUnit = draft.kind === "product"
? (draftTarget ? articleLabel(draftTarget) : "")
? (draftTarget ? dispLabel(draftTarget) : "")
: (draftTarget ? (draftTarget.min_stock_unit_name || "Stück") : "");
function resetAdd() {
@@ -143,15 +145,16 @@ export default function MinStock() {
if (draft.kind === "product") {
const p = draftTarget;
if (draft.scope === "global") {
// Menge in der Artikeleinheit erfasst → in Basiseinheiten speichern,
// wie beim Inline-Editieren ohne bestehenden Wert.
// Menge in der Anzeigeeinheit erfasst → in Basiseinheiten speichern.
await api.updateProduct(p.id, {
min_stock: Math.round(num * articleUnit(p)),
min_stock_in_packages: (p.package_size || 0) > 0,
min_stock_unit_id: null,
min_stock: Math.round(num * dispFactor(p)),
min_stock_in_packages: false,
min_stock_unit_id: p.display_unit_id ?? null,
});
} 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 {
const g = draftTarget;