Gruppen: Bestand in der richtigen Einheit, Baumansicht, Gruppen-Spalte

Anzeigefehler: die Gruppenliste klebte immer min_stock_unit_name hinter den
Bestand - auch wenn die Gruppe laengst im Gruppen-Gebinde zaehlt. Aus 790 g bei
einem 190-g-Glas wurde so "4,16 Gramm" statt "4,16 Glaeser". Gerechnet war
richtig, beschriftet falsch.

Ursache war doppelte Logik: MinStock.jsx hatte grpPkgMode/grpFactor/grpUnit
lokal, Groups.jsx gar nicht. Die drei stehen jetzt in units.js und werden von
beiden benutzt. Die Einheiten-Spalte sagt ausserdem, wenn eine Gruppe im
Gebinde zaehlt ("zaehlt in Glaeser a 190 g") und bietet den Weg zurueck an.

Gruppenliste als Baum: eine Gruppe mit zwei Obergruppen erscheint unter beiden -
das ist die ehrliche Darstellung eines Graphen. Jede Zeile traegt ihren PFAD als
Schluessel, damit die vorhandene Baumlogik der DataTable unveraendert zurecht
kommt; die kennt nur einen Elternteil je Zeile, und ein Pfad hat genau einen.
Beim Sortieren/Filtern flacht sie wie gewohnt ab.

"1 Untergruppen" heisst jetzt "1 Untergruppe" - an allen vier Stellen ueber
einen gemeinsamen Helfer (anzahlWort), Web und App.

Lebensmittelliste bekommt eine Spalte "Gruppe" mit Filter. Dafuer liefert
ProductOut jetzt group_name, symmetrisch zu category_name, mit joinedload gegen
N+1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-08-16 09:40:09 +02:00
co-authored by Claude Opus 5
parent 158175f693
commit 3de168b598
12 changed files with 148 additions and 31 deletions
+6 -10
View File
@@ -4,7 +4,7 @@ import { useAuth } from "../auth";
import { useConfirm } from "../confirm";
import Icon from "../components/Icon";
import { locationOptions, locationPathById } from "../locationPath";
import { fmt, gebinde, kindShort } from "../units";
import { anzahlWort, fmt, gebinde, grpFactor, grpPkgMode, grpUnit, kindShort } from "../units";
// Anzeigeeinheit eines Produkts: die im Formular gewählte Einheit (Gramm,
// Milliliter, Stück) – NICHT die Packung. „1 Packung" ist nichtssagend, weil eine
@@ -24,13 +24,9 @@ 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).
const grpPkgMode = (g) => !!g.min_stock_in_packages && !!(g.package_size && g.package_size > 0);
const grpFactor = (g) => (grpPkgMode(g) ? g.package_size : (g.min_stock_unit_factor || 1));
const grpUnit = (g, menge = 1) => (grpPkgMode(g)
? gebinde(menge, g.package_label || "Packung")
: (g.min_stock_unit_name || kindShort(g.kind) || ""));
// Gruppen rechnen in ihrer verwalteten Einheit ODER im Gruppen-Gebinde –
// grpPkgMode/grpFactor/grpUnit stehen in units.js, weil Groups.jsx sie genauso
// braucht (und ohne sie den Bestand falsch beschriftet hat).
// „Überall" ist der Ort NULL. Im <select> braucht es einen Wert, deshalb ein
// Platzhalter, der beim Speichern wieder zu null wird.
@@ -269,7 +265,7 @@ export default function MinStock() {
{g?.child_ids?.length > 0 && (
<span className="badge" style={{ marginLeft: 6 }}
title="Bestand und Mindestbestand zählen die Untergruppen mit.">
inkl. {g.child_ids.length} Untergruppen
inkl. {anzahlWort(g.child_ids.length, "Untergruppe", "Untergruppen")}
</span>
)}
</span>
@@ -375,7 +371,7 @@ export default function MinStock() {
{t.name}
{draft.kind === "product" && t.brand ? ` · ${t.brand}` : ""}
{draft.kind === "group" && t.child_ids?.length
? ` (inkl. ${t.child_ids.length} Untergruppen)` : ""}
? ` (inkl. ${anzahlWort(t.child_ids.length, "Untergruppe", "Untergruppen")})` : ""}
</option>
))}
</select>