Web: Mindestbestand nur fuer Charge-Artikel (Lebensmittel + Verbrauchsgegenstand)

Menge je Lagerort und Einzelstuecke haben keinen Mindestbestand mehr: die
"Mindestbestand (Stueck)"-Felder und der je-Lagerort-Block fallen im Formular
weg (Gruppe fuer Menge bleibt), Alt-Werte werden beim Speichern geleert. Die
Mindestbestaende-Seite und der "+ hinzufuegen"-Dialog zeigen nur noch
Charge-Artikel.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-07-28 08:54:50 +02:00
parent 275611160b
commit 49a96d7fdb
2 changed files with 23 additions and 45 deletions

View File

@@ -14,6 +14,9 @@ import { fmt } from "../units";
// 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");
// Mindestbestand gibt es nur bei Charge-Artikeln: Lebensmittel und
// Verbrauchsgegenstände (bulk). „Menge je Lagerort" und Einzelstücke nicht.
const canHaveMin = (p) => p.tracking !== "object" || p.bulk === true;
// 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));
@@ -50,7 +53,7 @@ export default function MinStock() {
const rows = useMemo(() => {
const out = [];
for (const p of products) {
for (const p of products.filter(canHaveMin)) {
out.push({
key: `p:${p.id}:global`, kind: "product", scope: "global", entity: p,
name: p.name, catId: p.category_id, ort: "(Gesamt)",
@@ -235,7 +238,7 @@ export default function MinStock() {
<select value={draft.targetId} onChange={(e) => setDraft({ ...draft, targetId: e.target.value })}>
<option value=""> wählen </option>
{(draft.kind === "product"
? [...products].sort((a, b) => a.name.localeCompare(b.name, "de"))
? products.filter(canHaveMin).sort((a, b) => a.name.localeCompare(b.name, "de"))
: [...groups].sort((a, b) => a.name.localeCompare(b.name, "de"))
).map((t) => (
<option key={t.id} value={t.id}>

View File

@@ -477,16 +477,19 @@ export default function ProductForm() {
...(isObject ? { field_values: fieldPayload } : {}),
};
}
// Gegenstand ohne Charge: Menge je Lagerort oder Einzelstücke. Mindestbestand
// in Stück (kein Einheit-Dropdown), gleiche Umrechnung liefert die Stückzahl.
// Menge-Gegenstände dürfen (wie Verbrauchsgegenstände) in eine Gruppe.
// Gegenstand ohne Charge: Menge je Lagerort oder Einzelstücke. Diese haben
// KEINEN Mindestbestand (den gibt es nur bei Lebensmitteln/Verbrauchs-
// gegenständen) etwaige Alt-Werte werden beim Speichern geleert. Gruppen
// sind weiter erlaubt (Menge je Lagerort).
return {
...base,
individual: form.individual,
bulk: false,
group_id: form.group_id === "" ? null : Number(form.group_id),
field_values: fieldPayload,
...minStock,
min_stock: null,
min_stock_unit_id: null,
min_stock_in_packages: false,
};
}
@@ -855,48 +858,20 @@ export default function ProductForm() {
</label>
</div>
)}
{objMode !== "bulk" && (
{/* Menge je Lagerort darf gruppiert werden aber ohne Mindestbestand
(den gibt es nur bei Lebensmitteln/Verbrauchsgegenständen). */}
{objMode === "count" && (
<div className="row">
<label className="grow" style={{ maxWidth: 300 }}>
Mindestbestand (Stück)
<input type="number" step="any" min="0" value={form.min_stock}
onChange={(e) => set("min_stock", e.target.value)} disabled={readOnly}
placeholder="z.B. 3" />
<span className="tip" title="Zählt Bestände mehrerer Gegenstände zusammen (in Stück).">
Gruppe
</span>
<select value={form.group_id} onChange={(e) => set("group_id", e.target.value)}
disabled={readOnly}>
<option value=""> keine </option>
{groups.map((g) => <option key={g.id} value={g.id}>{g.name}</option>)}
</select>
</label>
{objMode === "count" && (
<label className="grow" style={{ maxWidth: 300 }}>
<span className="tip" title="Zählt Bestände mehrerer Gegenstände zusammen (in Stück).">
Gruppe
</span>
<select value={form.group_id} onChange={(e) => set("group_id", e.target.value)}
disabled={readOnly}>
<option value=""> keine </option>
{groups.map((g) => <option key={g.id} value={g.id}>{g.name}</option>)}
</select>
</label>
)}
</div>
)}
{objMode !== "bulk" && !isNew && product && isAdmin && (
<div style={{ marginTop: "var(--sp-2)", marginBottom: "var(--sp-4)" }}>
<div className="card-head" style={{ marginBottom: "var(--sp-1)" }}>
<Icon name="location" size={16} />
<h3 style={{ margin: 0 }}>Mindestbestand je Lagerort</h3>
</div>
<p className="muted small mt-0">
Bedarf je Lagerort (z.B. Ferienhaus, Zuhause), zusätzlich zum Gesamt-Mindestbestand. Menge in Stück.
</p>
<LocationMinStock
locations={locations}
initial={product.location_min_stocks || []}
unitLabel="Stück"
onError={(m) => toast(m, "warn")}
onSave={async (list) => {
const updated = await api.setProductLocationMinStock(product.id, list);
setProduct(updated);
toast("Bedarfe je Lagerort gespeichert.");
}}
/>
</div>
)}
{form.category_id !== "" && (