Web: Obergruppen zuordnen, Mindestbestaende nach Lagerort

Mindestbestaende-Seite ist jetzt nach Lagerort gegliedert statt nach Artikel -
"Ueberall (egal wo)" zuerst, danach die Lagerorte. Es gibt keine automatische
Leerzeile je Artikel mehr; angelegt wird ueber "+ hinzufuegen" im jeweiligen
Abschnitt, dann steht der Ort schon fest und es fehlen nur Ziel und Menge.
Das Zeilenmodell faellt damit von vier Faellen auf zwei, und weil der Server
einheitlich Basiseinheiten liefert, bleibt genau eine Umrechnung uebrig statt
der drei gegenlaeufigen von vorher.

Gruppen: neue Spalte "Obergruppen" mit Mehrfachauswahl (GroupParentSelect).
Bewusst kein Baum - CategorySelect und die tree-Prop der DataTable koennen nur
einen Elternteil und wuerden eine Gruppe mit zwei Obergruppen doppelt anzeigen.
Die eigene Gruppe und ihre Untergruppen sind in der Auswahl gesperrt, damit die
Ringregel sichtbar wird, bevor der Server sie ablehnt. Produktzahl zeigt
transitiv und direkt ("12 (3 direkt)"), dazu Badges fuer Untergruppen und eine
Warnung, wenn eine Gruppe mit Untergruppen keine Einheit hat - sonst summiert
sie Gramm und Stueck zu einer sinnlosen Zahl.

Einkaufsliste: "Gesamt" heisst "Ueberall", Gruppen zeigen ihre mitgezaehlten
Untergruppen, und eine Fussnote benennt die eine Stelle, die sich nicht
verrechnen laesst (zwei Obergruppen mit gemeinsamer Untergruppe).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-08-15 23:56:18 +02:00
parent 54261b98c0
commit 0537a2b53f
9 changed files with 603 additions and 357 deletions

View File

@@ -33,7 +33,7 @@ export default function ShoppingList() {
{error && <div className="alert error"><Icon name="alert" size={16} />{error}</div>}
{byLocation.length > 0 && (
<div className="sub" style={{ marginBottom: "var(--sp-2)" }}>Gesamt</div>
<div className="sub" style={{ marginBottom: "var(--sp-2)" }}>Überall (egal wo)</div>
)}
<div className="card">
@@ -48,6 +48,11 @@ export default function ShoppingList() {
<label>
<input type="checkbox" checked={!!checked[key]} onChange={(e) => toggle(key, e.target.checked)} />
<span className="badge accent">Gruppe</span>
{it.subgroup_count > 0 && (
<span className="badge" title="Bestand und Fehlmenge zählen die Untergruppen mit; Käufe für Untergruppen sind bereits abgezogen.">
inkl. {it.subgroup_count} Untergruppen
</span>
)}
<span className="item-name">{it.name}</span>
</label>
<span className="muted small">
@@ -88,6 +93,11 @@ export default function ShoppingList() {
<label>
<input type="checkbox" checked={!!checked[key]} onChange={(e) => toggle(key, e.target.checked)} />
<span className="badge accent">Gruppe</span>
{it.subgroup_count > 0 && (
<span className="badge" title="Bestand und Fehlmenge zählen die Untergruppen mit; Käufe für Untergruppen sind bereits abgezogen.">
inkl. {it.subgroup_count} Untergruppen
</span>
)}
<span className="item-name">{it.name}</span>
</label>
<span className="muted small">
@@ -118,6 +128,14 @@ export default function ShoppingList() {
<p className="muted small">
Das Abhaken hilft beim Einkaufen und wird (noch) nicht dauerhaft gespeichert.
</p>
<p className="muted small">
Bedarfe sind gegeneinander verrechnet: Was du für einen Lagerort oder eine
Untergruppe kaufst, deckt Überall bzw. die Obergruppe mit ab. Hängt
dieselbe Untergruppe unter zwei Obergruppen, die einander <em>nicht</em>
enthalten (Grillwurst unter Wurst und unter Grillgut), stehen beide Bedarfe
getrennt da ein Kauf kann dann beide decken. Ein eigener Mindestbestand auf
der gemeinsamen Untergruppe verrechnet auch das.
</p>
</div>
);
}