Web: Einzelstücke mit QR – anlegen, je-Stück-Felder, Etiketten, /i/<uid>
Gegenstands-Produkte haben jetzt den Umschalter "Menge je Lagerort / Einzelstücke". Bei Einzelstücken erscheint (volle Breite) eine Liste je Stueck mit eigener UID, QR-Code, Lagerort, Kaufdatum, Garantie, Bezugsquelle und Notiz - direkt inline aenderbar, mit Entfernen (Grund) und druckbaren QR-Etiketten. Ein Scan oeffnet ueber die Route /i/<uid> das passende Stueck. QR via qrcode (gebundelt, offline). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@ import { useSettings } from "../settings";
|
||||
import { asTree } from "../categoryTree";
|
||||
import { DynamicFields, FIELD_TYPES } from "../fields";
|
||||
import ObjektBestand from "../components/ObjektBestand";
|
||||
import Einzelstuecke from "../components/Einzelstuecke";
|
||||
import {
|
||||
daysUntil, expiryRowClass, fmt, fromMonthInput, gebinde, isExpired, relativeExpiry,
|
||||
toMonthInput, unitShort,
|
||||
@@ -20,7 +21,7 @@ import {
|
||||
const EMPTY = {
|
||||
barcode: "", name: "", brand: "", image_url: "",
|
||||
unit_id: "", package_size: "", package_label: "", date_precision: "day", min_stock: "",
|
||||
group_id: "", category_id: "", shop_id: "", product_url: "",
|
||||
group_id: "", category_id: "", shop_id: "", product_url: "", individual: false,
|
||||
};
|
||||
|
||||
// (Gebinde kommen jetzt aus der Verwaltung, siehe api.listPackageTypes)
|
||||
@@ -384,6 +385,7 @@ export default function ProductForm() {
|
||||
category_id: p.category_id ?? "",
|
||||
shop_id: p.shop_id ?? "",
|
||||
product_url: p.product_url || "",
|
||||
individual: Boolean(p.individual),
|
||||
});
|
||||
setFieldValues(p.field_values || {});
|
||||
setModus(p.tracking === "object" ? "object" : "food");
|
||||
@@ -440,10 +442,11 @@ export default function ProductForm() {
|
||||
Object.entries(fieldValues).forEach(([k, v]) => {
|
||||
if (erlaubt.has(String(k))) fv[k] = v;
|
||||
});
|
||||
return { ...base, field_values: fv };
|
||||
return { ...base, individual: form.individual, field_values: fv };
|
||||
}
|
||||
return {
|
||||
...base,
|
||||
individual: false,
|
||||
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,
|
||||
@@ -740,13 +743,27 @@ export default function ProductForm() {
|
||||
{isObject && (<>
|
||||
<div className="row">
|
||||
<label className="grow">
|
||||
Gekauft bei
|
||||
<select value={form.shop_id} onChange={(e) => set("shop_id", e.target.value)}
|
||||
disabled={readOnly}>
|
||||
<option value="">– unbekannt / mehrere –</option>
|
||||
{shops.map((s) => <option key={s.id} value={s.id}>{s.name}</option>)}
|
||||
</select>
|
||||
Verwaltung
|
||||
<div className="segmented">
|
||||
<button type="button" className={!form.individual ? "active" : ""}
|
||||
onClick={() => set("individual", false)} disabled={readOnly}>Menge je Lagerort</button>
|
||||
<button type="button" className={form.individual ? "active" : ""}
|
||||
onClick={() => set("individual", true)} disabled={readOnly}>Einzelstücke</button>
|
||||
</div>
|
||||
</label>
|
||||
<div className="grow" />
|
||||
</div>
|
||||
<div className="row">
|
||||
{!form.individual && (
|
||||
<label className="grow">
|
||||
Gekauft bei
|
||||
<select value={form.shop_id} onChange={(e) => set("shop_id", e.target.value)}
|
||||
disabled={readOnly}>
|
||||
<option value="">– unbekannt / mehrere –</option>
|
||||
{shops.map((s) => <option key={s.id} value={s.id}>{s.name}</option>)}
|
||||
</select>
|
||||
</label>
|
||||
)}
|
||||
<label className="grow">
|
||||
Produktlink
|
||||
<div className="field-inline">
|
||||
@@ -831,7 +848,7 @@ export default function ProductForm() {
|
||||
</form>
|
||||
|
||||
{!isNew && (isObject ? (
|
||||
product && (
|
||||
product && !form.individual && (
|
||||
<ObjektBestand
|
||||
product={product}
|
||||
lots={lots}
|
||||
@@ -860,6 +877,18 @@ export default function ProductForm() {
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Einzelstücke über die volle Breite – die Tabelle hat viele Spalten. */}
|
||||
{!isNew && product && isObject && form.individual && (
|
||||
<Einzelstuecke
|
||||
product={product}
|
||||
locations={locations}
|
||||
shops={shops}
|
||||
isAdmin={isAdmin}
|
||||
onChanged={async () => { setProduct(await api.getProduct(id)); }}
|
||||
onError={setError}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Unterhalb der beiden Spalten: die Codes braucht man selten, sie sollen
|
||||
Formular und Chargen nicht auseinanderschieben. */}
|
||||
{!isNew && product && (
|
||||
|
||||
Reference in New Issue
Block a user