Web: Einheiten-Felder als zusammenhaengender Block
Im Grillwurst-Fall stand dieselbe Zahl zweimal im Formular, mit zwei Bedeutungen und in identischer Aufmachung: "Packungsgroesse 3" (1 Packung = 3 Stueck) und "Zweiteinheit 3 Stk ≙ 250 g" (3 Stueck wiegen 250 g). Dazu sass der Zweiteinheit-Block zwischen Gebinde/MHD und Mindestbestand/Gruppe, hiess nach einem Fachbegriff und benutzte ≙ statt eines Satzes. Jetzt steht alles zur Einheit unter einer Ueberschrift und liest sich als Satz: "Gezaehlt wird in Stueck", "Gebinde Packung", "1 Packung sind 3 Stueck". Das Gebinde steht dafuer vor der Packungsgroesse - die Zeile darunter nennt es beim Namen. Die Umrechnung heisst nach dem, was sie liefert (Gewicht / Volumen / Stueckzahl) und beginnt mit einer einzigen Auswahl; erst danach erscheint der Satz "3 Stk wiegen 250 g" mit passendem Verb und Ein-/Mehrzahl. Die linke Zahl wird beim Waehlen der Zielart EINMALIG mit der Packungsgroesse vorbelegt - zu tippen bleibt nur die 250. Bewusst keine dauerhafte Kopplung: wer die Packung spaeter auf 4 aendert, hat dieselbe Wurst, und ein mitgezogener Wert wuerde stillschweigend behaupten, sie sei leichter geworden. MHD-Angabe wandert aus dem Einheiten-Block zur Gruppen-Zeile; sie stand nur zufaellig neben dem Gebinde. Reine Oberflaeche - Datenmodell, API und Backend-Tests unveraendert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -590,6 +590,40 @@ export default function ProductForm() {
|
||||
const BASE_SHORT_OF_KIND = { count: "Stk", weight: "g", volume: "ml" };
|
||||
// Basiseinheit je Art – die eigene Art fliegt aus der Zweiteinheit-Auswahl.
|
||||
const BASE_OF_KIND = { count: "piece", weight: "gram", volume: "milliliter" };
|
||||
// Die Umrechnung heißt nach dem, was sie liefert – „Zweiteinheit" musste man
|
||||
// erst übersetzen. Ohne gewählte Zielart ein neutraler Titel.
|
||||
const ZWEIT_TITEL = { gram: "Gewicht", milliliter: "Volumen", piece: "Stückzahl" };
|
||||
// Verb passend zur Zielart und zur Anzahl: „1 Stück wiegt" / „3 Stück wiegen".
|
||||
const ZWEIT_VERB = {
|
||||
gram: ["wiegt", "wiegen"], milliliter: ["fasst", "fassen"], piece: ["ist", "sind"],
|
||||
};
|
||||
const zweitTitel = ZWEIT_TITEL[form.secondary_base] || "Umrechnung";
|
||||
const zweitVerb = (() => {
|
||||
const paar = ZWEIT_VERB[form.secondary_base];
|
||||
if (!paar) return "";
|
||||
return Number(form.secondary_count) === 1 ? paar[0] : paar[1];
|
||||
})();
|
||||
// Das Gebinde beim Namen nennen: „1 Packung sind …". Ohne Auswahl der
|
||||
// Standard, sonst stünde dort „1 sind 3 Stück".
|
||||
const gebindeName = form.package_label.trim() || "Packung";
|
||||
|
||||
// Zielart wählen: die linke Zahl einmalig mit der Packungsgröße vorbelegen –
|
||||
// im Normalfall ist sie genau das („1 Packung = 3 Stück, und die wiegen
|
||||
// 250 g"). BEWUSST keine dauerhafte Kopplung: wer die Packung später auf 4
|
||||
// ändert, hat dieselbe Wurst, und ein mitgezogener Wert würde stillschweigend
|
||||
// behaupten, sie sei leichter geworden.
|
||||
function waehleZweitart(basis) {
|
||||
if (basis && form.secondary_count === "") {
|
||||
setForm((f) => ({
|
||||
...f,
|
||||
secondary_base: basis,
|
||||
secondary_count: f.package_size !== "" ? f.package_size : "1",
|
||||
}));
|
||||
return;
|
||||
}
|
||||
set("secondary_base", basis);
|
||||
}
|
||||
|
||||
// Ausgerechneter Wert als Kontrolle: „1 Stk ≈ 83,33 g".
|
||||
const zweitHinweis = (() => {
|
||||
const f = zweitFaktor({
|
||||
@@ -600,6 +634,7 @@ export default function ProductForm() {
|
||||
if (!f) return "";
|
||||
return `1 ${BASE_SHORT_OF_KIND[selectedUnit?.kind] || "Einheit"} \u2248 `
|
||||
+ `${fmt(f)} ${unitShort(form.secondary_base)}. `
|
||||
+ "Damit zählt der Artikel auch in Gruppen, die in dieser Einheit rechnen. "
|
||||
+ "Bestände bleiben beim Ändern unverändert – nur ihre Umrechnung verschiebt sich.";
|
||||
})();
|
||||
const baseShort = selectedUnit
|
||||
@@ -799,9 +834,17 @@ export default function ProductForm() {
|
||||
{/* Lebensmittel ODER Verbrauchsgegenstand: Einheit, Packung, MHD,
|
||||
Mindestbestand (mit Einheit), Gruppe, Mindestbestand je Lagerort. */}
|
||||
{foodLike && (<>
|
||||
{/* Alles rund um Einheit, Packung und Umrechnung steht zusammen und
|
||||
liest sich als Satz. Vorher lagen die Felder verstreut, und die
|
||||
Packungsgroesse stand als blanke Zahl neben einer zweiten, gleich
|
||||
aussehenden Zahl aus der Umrechnung – das war nicht zu trennen. */}
|
||||
<div className="card-head" style={{ marginBottom: "var(--sp-1)" }}>
|
||||
<Icon name="package" size={16} />
|
||||
<h3 style={{ margin: 0 }}>Einheit und Menge</h3>
|
||||
</div>
|
||||
<div className="row">
|
||||
<label className="grow">
|
||||
Einheit
|
||||
Gezählt wird in
|
||||
<select value={form.unit_id} onChange={(e) => set("unit_id", e.target.value)} disabled={readOnly} required>
|
||||
<option value="">– wählen –</option>
|
||||
{KIND_ORDER.filter((k) => units.some((u) => u.kind === k)).map((k) => (
|
||||
@@ -813,13 +856,8 @@ export default function ProductForm() {
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label className="grow">
|
||||
Packungsgröße{baseShort ? ` (in ${baseShort})` : ""}
|
||||
<input type="number" step="any" value={form.package_size}
|
||||
onChange={(e) => set("package_size", e.target.value)} disabled={readOnly} placeholder="z.B. 500" />
|
||||
</label>
|
||||
</div>
|
||||
<div className="row">
|
||||
{/* Gebinde VOR der Packungsgröße: die Zeile darunter nennt es beim
|
||||
Namen („1 Packung sind …"). */}
|
||||
<label className="grow">
|
||||
<span className="tip" title="Verwaltet unter Verwaltung → Gebinde, dort auch mit Mehrzahl.">
|
||||
Gebinde
|
||||
@@ -837,46 +875,56 @@ export default function ProductForm() {
|
||||
)}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div className="row">
|
||||
<label className="grow">
|
||||
<span className="tip" title="Voreinstellung beim Einlagern – z.B. Konserven tragen oft nur „09/2026“.">
|
||||
MHD-Angabe
|
||||
1 {gebindeName} sind
|
||||
<div className="field-inline">
|
||||
<input type="number" step="any" style={{ maxWidth: 120 }} value={form.package_size}
|
||||
onChange={(e) => set("package_size", e.target.value)} disabled={readOnly} placeholder="z.B. 500" />
|
||||
<span className="muted small" style={{ alignSelf: "center" }}>{baseShort}</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* Umrechnung in die ANDERE Art: „3 Stück wiegen 250 g". Solange
|
||||
keine Zielart gewählt ist, steht hier nur eine Auswahl. */}
|
||||
<div className="card-head" style={{ marginBottom: "var(--sp-1)", marginTop: "var(--sp-2)" }}>
|
||||
<Icon name="split" size={16} />
|
||||
<h3 style={{ margin: 0 }}>{zweitTitel} (optional)</h3>
|
||||
</div>
|
||||
<div className="row">
|
||||
<label className="grow" style={{ maxWidth: 260 }}>
|
||||
<span className="tip" title="Zweite Lesart desselben Artikels – damit zählt er auch in Gruppen, die in einer anderen Einheit rechnen.">
|
||||
Umrechnen in
|
||||
</span>
|
||||
<select value={form.date_precision} onChange={(e) => set("date_precision", e.target.value)}
|
||||
disabled={readOnly}>
|
||||
<option value="day">Tagesdatum</option>
|
||||
<option value="month">nur Monat/Jahr</option>
|
||||
<select value={form.secondary_base} disabled={readOnly}
|
||||
onChange={(e) => waehleZweitart(e.target.value)}>
|
||||
<option value="">– keine –</option>
|
||||
{BASE_UNITS
|
||||
.filter((b) => !selectedUnit || b.value !== BASE_OF_KIND[selectedUnit.kind])
|
||||
.map((b) => <option key={b.value} value={b.value}>{b.label}</option>)}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
{/* Zweiteinheit: dieselbe Ware in der ANDEREN Art lesbar machen.
|
||||
Links steht die Basiseinheit des Artikels fest, rechts die
|
||||
Gegenart. Ohne Eintrag bleibt alles wie bisher getrennt. */}
|
||||
<div className="row">
|
||||
<label className="grow">
|
||||
<span className="tip" title="Zusätzliche Lesart desselben Artikels. Damit zählt er auch in Gruppen der anderen Art und lässt sich darin ein- und auslagern. Das Gebinde bleibt davon unberührt.">
|
||||
Zweiteinheit (optional)
|
||||
</span>
|
||||
{/* Der Satz traegt sich selbst – eine Beschriftung davor waere nur
|
||||
ein weiteres Wort zwischen zwei Zahlen. */}
|
||||
{form.secondary_base && (
|
||||
<div className="row">
|
||||
<div className="field-inline">
|
||||
<input type="number" step="any" min="0" style={{ maxWidth: 90 }}
|
||||
placeholder="3" value={form.secondary_count} disabled={readOnly}
|
||||
onChange={(e) => set("secondary_count", e.target.value)} />
|
||||
<span className="muted small" style={{ alignSelf: "center" }}>
|
||||
{baseShort || "Einheiten"} ≙
|
||||
<span style={{ alignSelf: "center" }}>
|
||||
{baseShort || "Einheiten"} {zweitVerb}
|
||||
</span>
|
||||
<input type="number" step="any" min="0" style={{ maxWidth: 90 }}
|
||||
placeholder="250" value={form.secondary_amount} disabled={readOnly}
|
||||
onChange={(e) => set("secondary_amount", e.target.value)} />
|
||||
<select value={form.secondary_base} disabled={readOnly}
|
||||
style={{ marginTop: 0, maxWidth: 150 }}
|
||||
onChange={(e) => set("secondary_base", e.target.value)}>
|
||||
<option value="">– keine –</option>
|
||||
{BASE_UNITS
|
||||
.filter((b) => !selectedUnit || b.value !== BASE_OF_KIND[selectedUnit.kind])
|
||||
.map((b) => <option key={b.value} value={b.value}>{b.label}</option>)}
|
||||
</select>
|
||||
<span style={{ alignSelf: "center" }}>{unitShort(form.secondary_base)}</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{zweitHinweis && <p className="muted small mt-0">{zweitHinweis}</p>}
|
||||
<div className="row">
|
||||
{/* Beim Anlegen gibt es den Artikel noch nicht, also auch keine
|
||||
@@ -904,6 +952,16 @@ export default function ProductForm() {
|
||||
</div>
|
||||
</label>
|
||||
)}
|
||||
<label className="grow">
|
||||
<span className="tip" title="Voreinstellung beim Einlagern – z.B. Konserven tragen oft nur „09/2026“.">
|
||||
MHD-Angabe
|
||||
</span>
|
||||
<select value={form.date_precision} onChange={(e) => set("date_precision", e.target.value)}
|
||||
disabled={readOnly}>
|
||||
<option value="day">Tagesdatum</option>
|
||||
<option value="month">nur Monat/Jahr</option>
|
||||
</select>
|
||||
</label>
|
||||
<label className="grow">
|
||||
<span className="tip" title="Zählt Bestände mehrerer Artikel zusammen. Der EAN-Code dieses Artikels erscheint danach automatisch bei der Gruppe.">
|
||||
Gruppe
|
||||
|
||||
Reference in New Issue
Block a user