Gebinde-Bezeichnung je Produkt + Spalte "Einheiten (ist / soll)"
- Neues Feld products.package_label: pro Artikel einstellbar, wie das Gebinde heisst (Packung, Glas, Tuete, Flasche, Dose, Tube, ...) - Eingabe mit Vorschlagsliste, frei ueberschreibbar. Migration per ADD COLUMN IF NOT EXISTS. - Produkttabelle: Spalte "Packungen" heisst jetzt "Einheiten (ist / soll)" und zeigt Bestand und Mindestbestand als "11 / 12 Glas"; die separate Spalte "Mindest" entfaellt. Das "niedrig"-Badge sitzt jetzt dort. - Die Bezeichnung wird ueberall verwendet (Chargenanzeige, Einheiten-Umschalter beim Bearbeiten, Mindestbestand-Auswahl, Umrechnungshinweis). - Speichern/Abbrechen im Chargen-Editor sind jetzt gleich breit (.btn-pair). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -8,9 +8,12 @@ import { daysUntil, expiryRowClass, fmt, isExpired, unitShort } from "../units";
|
||||
|
||||
const EMPTY = {
|
||||
barcode: "", name: "", brand: "", image_url: "",
|
||||
unit_id: "", package_size: "", min_stock: "", group_id: "",
|
||||
unit_id: "", package_size: "", package_label: "", min_stock: "", group_id: "",
|
||||
};
|
||||
|
||||
// Vorschläge für die Gebinde-Bezeichnung (frei überschreibbar).
|
||||
const PACKAGE_LABELS = ["Packung", "Glas", "Tüte", "Flasche", "Dose", "Tube", "Becher", "Beutel", "Karton"];
|
||||
|
||||
// Kanonische Einheit je Basiseinheit (für OFF-Vorschläge und Altbestand).
|
||||
const CANONICAL_NAME = { piece: "Stück", gram: "Gramm", milliliter: "Milliliter" };
|
||||
|
||||
@@ -135,7 +138,7 @@ export default function ProductForm() {
|
||||
setForm({
|
||||
barcode: p.barcode || "", name: p.name, brand: p.brand || "",
|
||||
image_url: p.image_url || "", unit_id: uid,
|
||||
package_size: p.package_size ?? "",
|
||||
package_size: p.package_size ?? "", package_label: p.package_label || "",
|
||||
min_stock: p.min_stock != null ? p.min_stock / f : "",
|
||||
group_id: p.group_id ?? "",
|
||||
});
|
||||
@@ -168,6 +171,7 @@ export default function ProductForm() {
|
||||
image_url: form.image_url || null,
|
||||
unit_id: form.unit_id === "" ? null : Number(form.unit_id),
|
||||
package_size: form.package_size === "" ? null : Number(form.package_size),
|
||||
package_label: form.package_label.trim() || null,
|
||||
min_stock: minBase,
|
||||
min_stock_unit_id: sel === "package" || sel === "" ? null : Number(sel),
|
||||
min_stock_in_packages: sel === "package",
|
||||
@@ -271,6 +275,18 @@ export default function ProductForm() {
|
||||
onChange={(e) => set("package_size", e.target.value)} disabled={readOnly} placeholder="z.B. 500" />
|
||||
</label>
|
||||
</div>
|
||||
<div className="row">
|
||||
<label className="grow">
|
||||
Bezeichnung der Einheit
|
||||
<input list="package-labels" value={form.package_label}
|
||||
onChange={(e) => set("package_label", e.target.value)} disabled={readOnly}
|
||||
placeholder="Packung" />
|
||||
<datalist id="package-labels">
|
||||
{PACKAGE_LABELS.map((l) => <option key={l} value={l} />)}
|
||||
</datalist>
|
||||
</label>
|
||||
<div className="grow" />
|
||||
</div>
|
||||
<div className="row">
|
||||
<label className="grow">
|
||||
Mindestbestand
|
||||
@@ -284,7 +300,7 @@ export default function ProductForm() {
|
||||
.map((u) => <option key={u.id} value={u.id}>{u.name}</option>)}
|
||||
{form.package_size && (
|
||||
<option value="package">
|
||||
Packung(en) à {fmt(form.package_size)} {baseShort}
|
||||
{form.package_label.trim() || "Packung"} à {fmt(form.package_size)} {baseShort}
|
||||
</option>
|
||||
)}
|
||||
</select>
|
||||
@@ -342,11 +358,12 @@ function LotsCard({ product, lots, baseShort, warnDays, isAdmin, imageUrl, onCha
|
||||
const unitFactor = product?.unit_factor || 1;
|
||||
const unitName = product?.unit_name || baseShort;
|
||||
const pkgSize = product?.package_size || 0;
|
||||
const pkgLabel = product?.package_label || "Packung";
|
||||
|
||||
// Leitangabe: Packungen, sobald eine Packungsgröße hinterlegt ist –
|
||||
// sonst die Produkteinheit. Darunter steht die jeweils andere Angabe klein.
|
||||
const primaryFactor = pkgSize > 0 ? pkgSize : unitFactor;
|
||||
const primaryLabel = pkgSize > 0 ? "Pkg" : unitName;
|
||||
const primaryLabel = pkgSize > 0 ? pkgLabel : unitName;
|
||||
|
||||
function secondary(qty) {
|
||||
if (pkgSize > 0) return `${fmt(qty / unitFactor)} ${unitName}`;
|
||||
@@ -434,7 +451,7 @@ function LotsCard({ product, lots, baseShort, warnDays, isAdmin, imageUrl, onCha
|
||||
{pkgSize > 0 ? (
|
||||
<select value={editUnit} onChange={(e) => changeEditUnit(e.target.value)}
|
||||
style={{ marginTop: 0 }}>
|
||||
<option value="package">Packung(en)</option>
|
||||
<option value="package">{pkgLabel}</option>
|
||||
<option value="unit">{unitName}</option>
|
||||
</select>
|
||||
) : (
|
||||
@@ -466,7 +483,7 @@ function LotsCard({ product, lots, baseShort, warnDays, isAdmin, imageUrl, onCha
|
||||
<td className="muted">{new Date(l.created_at).toLocaleDateString("de-DE")}</td>
|
||||
<td className="num">
|
||||
{isAdmin && (editing ? (
|
||||
<div className="field-inline" style={{ justifyContent: "flex-end" }}>
|
||||
<div className="btn-pair">
|
||||
<button className="btn sm primary" onClick={() => saveEdit(l)}>Speichern</button>
|
||||
<button className="btn sm" onClick={() => setEditId(null)}>Abbrechen</button>
|
||||
</div>
|
||||
@@ -489,7 +506,7 @@ function LotsCard({ product, lots, baseShort, warnDays, isAdmin, imageUrl, onCha
|
||||
</table>
|
||||
</div>
|
||||
{pkgSize > 0 && (
|
||||
<p className="muted small">1 Packung = {fmt(pkgSize)} {baseShort}</p>
|
||||
<p className="muted small">1 {pkgLabel} = {fmt(pkgSize)} {baseShort}</p>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user