Compare commits
2 Commits
49a96d7fdb
...
27f84003f3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
27f84003f3 | ||
|
|
3ff7d2a35a |
@@ -61,7 +61,8 @@ struct ProductDetailView: View {
|
||||
if current.isObject {
|
||||
if !current.isIndividual && !current.isBulk, shopId != current.shopId { return true }
|
||||
if !current.isBulk, productUrl != (current.productUrl ?? "") { return true }
|
||||
if minStock != (current.minStockDisplay.map { formatAmount($0) } ?? "") { return true }
|
||||
// Mindestbestand gibt es nur beim Verbrauchsgegenstand.
|
||||
if current.isBulk, minStock != (current.minStockDisplay.map { formatAmount($0) } ?? "") { return true }
|
||||
if fieldValues != Self.stringValues(current.fieldValues) { return true }
|
||||
}
|
||||
return false
|
||||
@@ -172,13 +173,16 @@ struct ProductDetailView: View {
|
||||
}
|
||||
}
|
||||
|
||||
if current.isObject {
|
||||
// Mindestbestand nur bei Verbrauchsgegenständen (Charge) – in der
|
||||
// Anzeigeeinheit (z.B. Milliliter), nicht in Packungen. Menge je
|
||||
// Lagerort und Einzelstücke haben keinen Mindestbestand.
|
||||
if current.isBulk {
|
||||
Section("Mindestbestand") {
|
||||
QuantityField(label: "Gesamt (\(current.isBulk ? current.articleUnitLabel : "Stück"))",
|
||||
text: $minStock)
|
||||
QuantityField(label: "Gesamt (\(current.unitName))", text: $minStock)
|
||||
}
|
||||
}
|
||||
|
||||
if current.foodLike {
|
||||
Section {
|
||||
NavigationLink {
|
||||
ProductLocationMinView(product: current) { await reload() }
|
||||
@@ -190,6 +194,7 @@ struct ProductDetailView: View {
|
||||
} footer: {
|
||||
Text("Bedarf je Lagerort (z.B. Ferienhaus, Zuhause), zusätzlich zum Gesamt-Mindestbestand.")
|
||||
}
|
||||
}
|
||||
|
||||
Section("Erkennung") {
|
||||
LabeledContent("Barcode", value: current.barcode ?? "–")
|
||||
@@ -326,8 +331,9 @@ struct ProductDetailView: View {
|
||||
busy = true
|
||||
defer { busy = false }
|
||||
do {
|
||||
// Mindestbestand in der Anzeigeeinheit erfasst → Basiseinheiten.
|
||||
let minBase = Double(minStock.replacingOccurrences(of: ",", with: "."))
|
||||
.map { $0 * current.articleUnitFactor }
|
||||
.map { $0 * current.unitFactor }
|
||||
if current.foodLike {
|
||||
// Lebensmittel ODER Verbrauchsgegenstand: als Charge mit Einheit/
|
||||
// Packung/MHD/Gruppe. Verbrauchsgegenstände (isObject) tragen
|
||||
@@ -346,15 +352,16 @@ struct ProductDetailView: View {
|
||||
for def in fieldDefs { fv[String(def.id)] = fieldValues[String(def.id)] ?? "" }
|
||||
req.fieldValues = fv
|
||||
req.minStock = minBase
|
||||
req.minStockInPackages = (current.packageSize ?? 0) > 0
|
||||
req.minStockInPackages = false
|
||||
req.minStockUnitId = nil
|
||||
req.sendMinStock = true
|
||||
}
|
||||
current = try await APIClient.shared.updateProduct(id: current.id, req)
|
||||
if current.isObject { fieldValues = Self.stringValues(current.fieldValues) }
|
||||
} else {
|
||||
// Gegenstand: Menge je Lagerort oder Einzelstücke. Menge-
|
||||
// Gegenstände dürfen jetzt ebenfalls in eine Gruppe.
|
||||
// Gegenstand: Menge je Lagerort oder Einzelstücke. Kein
|
||||
// Mindestbestand (Alt-Werte werden geleert); Menge-Gegenstände
|
||||
// dürfen weiter in eine Gruppe.
|
||||
var fv: [String: String?] = [:]
|
||||
for def in fieldDefs { fv[String(def.id)] = fieldValues[String(def.id)] ?? "" }
|
||||
current = try await APIClient.shared.updateProduct(
|
||||
@@ -368,8 +375,8 @@ struct ProductDetailView: View {
|
||||
shopId: current.isIndividual ? nil : shopId,
|
||||
productUrl: productUrl.isEmpty ? nil : productUrl,
|
||||
fieldValues: fv,
|
||||
minStock: minBase,
|
||||
minStockInPackages: (current.packageSize ?? 0) > 0,
|
||||
minStock: nil,
|
||||
minStockInPackages: false,
|
||||
minStockUnitId: nil,
|
||||
sendMinStock: true
|
||||
)
|
||||
@@ -550,10 +557,16 @@ struct ProductLocationMinView: View {
|
||||
@State private var busy = false
|
||||
@State private var error: String?
|
||||
|
||||
// Speicherung in Artikeleinheiten, Anzeige in der Anzeigeeinheit (Gramm/ml/Stück).
|
||||
private var artToDisp: Double {
|
||||
let disp = product.unitFactor == 0 ? 1 : product.unitFactor
|
||||
return product.articleUnitFactor / disp
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section {
|
||||
Text("Bedarf je Lagerort (z.B. Ferienhaus, Zuhause). Menge in \(product.articleUnitLabel).")
|
||||
Text("Bedarf je Lagerort (z.B. Ferienhaus, Zuhause). Menge in \(product.unitName).")
|
||||
.font(.caption).foregroundStyle(.secondary)
|
||||
}
|
||||
ForEach($rows) { $row in
|
||||
@@ -597,7 +610,7 @@ struct ProductLocationMinView: View {
|
||||
private func load() async {
|
||||
locations = (try? await APIClient.shared.locations()) ?? []
|
||||
rows = (product.locationMinStocks ?? []).map {
|
||||
MinRow(locationId: $0.locationId, amount: formatAmount($0.minStock))
|
||||
MinRow(locationId: $0.locationId, amount: formatAmount($0.minStock * artToDisp))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -611,7 +624,8 @@ struct ProductLocationMinView: View {
|
||||
let wert = Double(row.amount.replacingOccurrences(of: ",", with: ".")) ?? 0
|
||||
if wert > 0 {
|
||||
gesehen.insert(loc)
|
||||
list.append(LocationMinStockIn(locationId: loc, minStock: wert))
|
||||
// Eingabe in Anzeigeeinheit → Artikeleinheiten (so gespeichert).
|
||||
list.append(LocationMinStockIn(locationId: loc, minStock: wert / artToDisp))
|
||||
}
|
||||
}
|
||||
do {
|
||||
|
||||
@@ -34,6 +34,13 @@ const CANONICAL_NAME = { piece: "Stück", gram: "Gramm", milliliter: "Milliliter
|
||||
const KIND_LABEL = { count: "Anzahl", weight: "Gewicht", volume: "Volumen" };
|
||||
const KIND_ORDER = ["count", "weight", "volume"];
|
||||
|
||||
// Faktor Artikeleinheit → Anzeigeeinheit: die je-Lagerort-Mindestbestände sind in
|
||||
// Artikeleinheiten (Packungen) gespeichert, angezeigt wird aber die echte Einheit
|
||||
// (Gramm/Milliliter/Stück), damit „Packung" nicht nichtssagend dasteht.
|
||||
const artToDisp = (p) => (
|
||||
(p.package_size > 0 ? p.package_size : (p.unit_factor || 1)) / (p.unit_factor || 1)
|
||||
);
|
||||
|
||||
// Foto vor dem Hochladen verkleinern: kleiner in der DB, schneller im Upload und
|
||||
// zuverlässig unter dem Server-Limit. Bei Problemen fällt es aufs Original zurück.
|
||||
function verkleinereBild(datei, maxDim = 1600, quality = 0.85) {
|
||||
@@ -812,16 +819,19 @@ export default function ProductForm() {
|
||||
</div>
|
||||
<p className="muted small mt-0">
|
||||
Zusätzlich zum Gesamt-Mindestbestand: eigener Bedarf je Lagerort (z.B.
|
||||
Ferienhaus, Zuhause). Menge in {product.package_label || product.unit_name || "Stück"}.
|
||||
Ferienhaus, Zuhause). Menge in {product.unit_name || "Stück"}.
|
||||
Wird separat gespeichert.
|
||||
</p>
|
||||
<LocationMinStock
|
||||
locations={locations}
|
||||
initial={product.location_min_stocks || []}
|
||||
unitLabel={product.package_label || product.unit_name || "Stück"}
|
||||
initial={(product.location_min_stocks || []).map(
|
||||
(e) => ({ ...e, min_stock: e.min_stock * artToDisp(product) }))}
|
||||
unitLabel={product.unit_name || "Stück"}
|
||||
onError={(m) => toast(m, "warn")}
|
||||
onSave={async (list) => {
|
||||
const updated = await api.setProductLocationMinStock(product.id, list);
|
||||
const f = artToDisp(product);
|
||||
const artList = list.map((e) => ({ ...e, min_stock: e.min_stock / f }));
|
||||
const updated = await api.setProductLocationMinStock(product.id, artList);
|
||||
setProduct(updated);
|
||||
toast("Bedarfe je Lagerort gespeichert.");
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user