diff --git a/backend/app/routers/views.py b/backend/app/routers/views.py
index ba93e97..2f1b35e 100644
--- a/backend/app/routers/views.py
+++ b/backend/app/routers/views.py
@@ -32,6 +32,7 @@ def shopping_list(
product_id=product.id,
name=product.name,
base_unit=product.base_unit,
+ package_size=product.package_size,
stock=stock,
min_stock=product.min_stock,
deficit=product.min_stock - stock,
diff --git a/backend/app/schemas.py b/backend/app/schemas.py
index af6af1c..2182211 100644
--- a/backend/app/schemas.py
+++ b/backend/app/schemas.py
@@ -182,6 +182,7 @@ class ShoppingItem(BaseModel):
product_id: int
name: str
base_unit: BaseUnit
+ package_size: float | None = None
stock: float
min_stock: float
deficit: float
diff --git a/web/src/pages/Dashboard.jsx b/web/src/pages/Dashboard.jsx
index eb4c8ad..f6848d8 100644
--- a/web/src/pages/Dashboard.jsx
+++ b/web/src/pages/Dashboard.jsx
@@ -2,7 +2,7 @@ import { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import { api } from "../api";
import Icon from "../components/Icon";
-import { fmt, unitShort } from "../units";
+import { amountText, fmt, unitShort } from "../units";
export default function Dashboard() {
const [expiring, setExpiring] = useState([]);
@@ -115,8 +115,8 @@ export default function Dashboard() {
{shopping.map((it) => (
| {it.name} |
- {fmt(it.stock)} {unitShort(it.base_unit)} |
- {fmt(it.deficit)} {unitShort(it.base_unit)} |
+ {amountText(it.stock, it.package_size, it.base_unit)} |
+ {amountText(it.deficit, it.package_size, it.base_unit)} |
))}
diff --git a/web/src/pages/ProductForm.jsx b/web/src/pages/ProductForm.jsx
index 3631832..86ed8f3 100644
--- a/web/src/pages/ProductForm.jsx
+++ b/web/src/pages/ProductForm.jsx
@@ -25,11 +25,23 @@ export default function ProductForm() {
const [error, setError] = useState(null);
const [info, setInfo] = useState(null);
const [busy, setBusy] = useState(false);
+ // Einheit, in der der Mindestbestand eingegeben wird: "base" oder "package".
+ const [minUnit, setMinUnit] = useState("base");
function set(k, v) {
setForm((f) => ({ ...f, [k]: v }));
}
+ // Wechselt die Mindestbestand-Einheit und rechnet den angezeigten Wert um.
+ function changeMinUnit(newUnit) {
+ const ps = Number(form.package_size);
+ if (form.min_stock !== "" && ps > 0 && newUnit !== minUnit) {
+ const v = Number(form.min_stock);
+ set("min_stock", String(newUnit === "package" ? v / ps : v * ps));
+ }
+ setMinUnit(newUnit);
+ }
+
function applySuggestion(s, groupsList) {
const groupGuess = guessGroup(groupsList, s);
setForm((f) => ({
@@ -42,6 +54,7 @@ export default function ProductForm() {
package_size: s.package_size != null ? String(s.package_size) : f.package_size,
group_id: f.group_id || groupGuess,
}));
+ if (s.package_size != null) setMinUnit("package");
setInfo(
"Daten von Open Food Facts übernommen." +
(s.package_size != null ? "" : " (Füllmenge nicht hinterlegt – bitte Packungsgröße prüfen.)") +
@@ -76,10 +89,19 @@ export default function ProductForm() {
if (!isNew) {
const p = await api.getProduct(id);
setProduct(p);
+ // Mindestbestand ist in Basiseinheiten gespeichert; bei Packungsprodukten
+ // zeigen wir ihn zur besseren Verständlichkeit in Packungen an.
+ let minDisplay = p.min_stock ?? "";
+ if (p.min_stock != null && p.package_size && p.package_size > 0) {
+ minDisplay = p.min_stock / p.package_size;
+ setMinUnit("package");
+ } else {
+ setMinUnit("base");
+ }
setForm({
barcode: p.barcode || "", name: p.name, brand: p.brand || "",
image_url: p.image_url || "", base_unit: p.base_unit,
- package_size: p.package_size ?? "", min_stock: p.min_stock ?? "",
+ package_size: p.package_size ?? "", min_stock: minDisplay,
group_id: p.group_id ?? "",
});
setLots(await api.listLots(id));
@@ -99,6 +121,12 @@ export default function ProductForm() {
}, [id]);
function buildPayload() {
+ const ps = Number(form.package_size);
+ let minBase = null;
+ if (form.min_stock !== "") {
+ const v = Number(form.min_stock);
+ minBase = minUnit === "package" && ps > 0 ? v * ps : v;
+ }
return {
barcode: form.barcode || null,
name: form.name,
@@ -106,7 +134,7 @@ export default function ProductForm() {
image_url: form.image_url || null,
base_unit: form.base_unit,
package_size: form.package_size === "" ? null : Number(form.package_size),
- min_stock: form.min_stock === "" ? null : Number(form.min_stock),
+ min_stock: minBase,
group_id: form.group_id === "" ? null : Number(form.group_id),
};
}
@@ -195,8 +223,16 @@ export default function ProductForm() {
- fehlt {fmt(it.deficit)} {unitShort(it.base_unit)}{" "}
- (Bestand {fmt(it.stock)} / min {fmt(it.min_stock)})
+ fehlt {amountText(it.deficit, it.package_size, it.base_unit)}{" "}
+ (Bestand {amountText(it.stock, it.package_size, it.base_unit)})
);
diff --git a/web/src/units.js b/web/src/units.js
index a803826..ca21c8a 100644
--- a/web/src/units.js
+++ b/web/src/units.js
@@ -36,3 +36,11 @@ export function fmt(n) {
if (n == null) return "–";
return Number(n).toLocaleString("de-DE", { maximumFractionDigits: 2 });
}
+
+// Menge lesbar darstellen: bei Packungsprodukten in Packungen (+ Basiseinheit in Klammern).
+export function amountText(qty, packageSize, baseUnit) {
+ if (packageSize && packageSize > 0) {
+ return `${fmt(qty / packageSize)} Pkg (${fmt(qty)} ${unitShort(baseUnit)})`;
+ }
+ return `${fmt(qty)} ${unitShort(baseUnit)}`;
+}