iOS: Verbrauchsgegenstand als dritte Gegenstands-Art (wie ein Lebensmittel)
Neuer Artikel: 3-Wege-Verwaltung fuer Gegenstaende (Menge je Lagerort / Einzelstuecke / Verbrauchsgegenstand). Verbrauchsgegenstaende laufen wie Lebensmittel als Charge (Einheit, Packung, MHD, Gruppe, Chargen-Ansicht, Mindestbestand mit Einheit) - foodLike = kein Gegenstand ODER bulk. Menge- Gegenstaende koennen jetzt ebenfalls einer Gruppe zugeordnet werden. ProductDetailView entsprechend auf foodLike umgestellt; NewProductRequest und ProductUpdateRequest um bulk/individual/base_unit erweitert. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -50,16 +50,19 @@ struct ProductDetailView: View {
|
||||
if name != current.name { return true }
|
||||
if brand != (current.brand ?? "") { return true }
|
||||
if categoryId != current.categoryId { return true }
|
||||
if current.isObject {
|
||||
if !current.isIndividual, shopId != current.shopId { return true }
|
||||
if productUrl != (current.productUrl ?? "") { return true }
|
||||
if minStock != (current.minStockDisplay.map { formatAmount($0) } ?? "") { return true }
|
||||
if fieldValues != Self.stringValues(current.fieldValues) { return true }
|
||||
} else {
|
||||
// Gruppe gibt es für alles außer Einzelstücke.
|
||||
if !current.isIndividual, groupId != current.groupId { return true }
|
||||
// Packung/MHD nur bei Lebensmitteln und Verbrauchsgegenständen.
|
||||
if current.foodLike {
|
||||
if packageSize != (current.packageSize.map { formatAmount($0) } ?? "") { return true }
|
||||
if packageLabel != (current.packageLabel ?? "") { return true }
|
||||
if datePrecision != (current.datePrecision == "month" ? "month" : "day") { return true }
|
||||
if groupId != current.groupId { return true }
|
||||
}
|
||||
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 }
|
||||
if fieldValues != Self.stringValues(current.fieldValues) { return true }
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -82,10 +85,12 @@ struct ProductDetailView: View {
|
||||
if current.isObject && current.isIndividual {
|
||||
ProductItemsSection(product: current, onChanged: { await reload() },
|
||||
onFinish: { dismiss() })
|
||||
} else if current.isObject {
|
||||
} else if current.isObject && !current.isBulk {
|
||||
// Menge je Lagerort (Verbrauchsgegenstände laufen als Charge, s.u.).
|
||||
ObjectStockSection(product: current, locations: locations, lots: lots,
|
||||
onChanged: { await reload() })
|
||||
} else {
|
||||
// Lebensmittel und Verbrauchsgegenstände: Vorrat aus den Chargen.
|
||||
Section("Bestand") {
|
||||
LabeledContent("Vorrat",
|
||||
value: "\(formatAmount(current.stockInArticleUnits)) \(current.articleUnitLabel)")
|
||||
@@ -101,7 +106,7 @@ struct ProductDetailView: View {
|
||||
Section("Artikel") {
|
||||
LabeledField(label: "Name", text: $name)
|
||||
LabeledField(label: "Marke", text: $brand)
|
||||
if !current.isObject {
|
||||
if current.foodLike {
|
||||
QuantityField(label: "Packungsgröße", text: $packageSize,
|
||||
suffix: display.baseUnitLabel(current.baseUnit))
|
||||
Picker("Bezeichnung", selection: $packageLabel) {
|
||||
@@ -123,7 +128,9 @@ struct ProductDetailView: View {
|
||||
: "Nur Lebensmittel-Kategorien.")
|
||||
}
|
||||
|
||||
if current.isObject {
|
||||
// Bezugsquelle/Link nur für Menge je Lagerort und Einzelstücke –
|
||||
// Verbrauchsgegenstände laufen wie Lebensmittel und lassen das weg.
|
||||
if current.isObject && !current.isBulk {
|
||||
// „Gekauft bei" gehört bei Einzelstücken zum einzelnen Stück
|
||||
// (jedes kann woanders gekauft sein), nicht ans Modell – wie im
|
||||
// Web wird der Shop hier dann ausgeblendet. Der Produktlink
|
||||
@@ -140,14 +147,10 @@ struct ProductDetailView: View {
|
||||
Link("Im Onlineshop öffnen", destination: url)
|
||||
}
|
||||
}
|
||||
if !fieldDefs.isEmpty {
|
||||
Section("Eigene Felder") {
|
||||
ForEach(fieldDefs) { def in
|
||||
ObjectFieldRow(def: def, value: fieldBinding(def))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
// Gruppe für alles außer Einzelstücke (Lebensmittel, Verbrauchs-
|
||||
// gegenstand, Menge je Lagerort).
|
||||
if !current.isIndividual {
|
||||
Section {
|
||||
Picker("Gruppe", selection: $groupId) {
|
||||
Text("– keine –").tag(Int?.none)
|
||||
@@ -156,14 +159,23 @@ struct ProductDetailView: View {
|
||||
}
|
||||
}
|
||||
} footer: {
|
||||
Text("Gruppe: zählt Bestände mehrerer Marken zusammen. Der EAN-Code "
|
||||
Text("Gruppe: zählt Bestände mehrerer Artikel zusammen. Der EAN-Code "
|
||||
+ "wandert bei einem Wechsel mit.")
|
||||
}
|
||||
}
|
||||
// Eigene Felder für alle Gegenstände mit Kategorie-Feldern.
|
||||
if current.isObject && !fieldDefs.isEmpty {
|
||||
Section("Eigene Felder") {
|
||||
ForEach(fieldDefs) { def in
|
||||
ObjectFieldRow(def: def, value: fieldBinding(def))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if current.isObject {
|
||||
Section("Mindestbestand") {
|
||||
QuantityField(label: "Gesamt (Stück)", text: $minStock)
|
||||
QuantityField(label: "Gesamt (\(current.isBulk ? current.articleUnitLabel : "Stück"))",
|
||||
text: $minStock)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,7 +203,7 @@ struct ProductDetailView: View {
|
||||
.disabled(busy || name.isEmpty)
|
||||
}
|
||||
|
||||
if !current.isObject {
|
||||
if current.foodLike {
|
||||
Section("Chargen") {
|
||||
ForEach(lots) { lot in
|
||||
Button {
|
||||
@@ -314,8 +326,35 @@ struct ProductDetailView: View {
|
||||
busy = true
|
||||
defer { busy = false }
|
||||
do {
|
||||
if current.isObject {
|
||||
// Gegenstände: keine Lebensmittel-Felder, dafür Shop, Link und eigene Felder.
|
||||
let minBase = Double(minStock.replacingOccurrences(of: ",", with: "."))
|
||||
.map { $0 * current.articleUnitFactor }
|
||||
if current.foodLike {
|
||||
// Lebensmittel ODER Verbrauchsgegenstand: als Charge mit Einheit/
|
||||
// Packung/MHD/Gruppe. Verbrauchsgegenstände (isObject) tragen
|
||||
// zusätzlich eigene Felder und einen Mindestbestand.
|
||||
var req = ProductUpdateRequest(
|
||||
name: name,
|
||||
brand: brand.isEmpty ? nil : brand,
|
||||
packageSize: Double(packageSize.replacingOccurrences(of: ",", with: ".")),
|
||||
packageLabel: packageLabel.isEmpty ? nil : packageLabel,
|
||||
datePrecision: datePrecision,
|
||||
groupId: groupId,
|
||||
categoryId: categoryId
|
||||
)
|
||||
if current.isObject {
|
||||
var fv: [String: String?] = [:]
|
||||
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.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.
|
||||
var fv: [String: String?] = [:]
|
||||
for def in fieldDefs { fv[String(def.id)] = fieldValues[String(def.id)] ?? "" }
|
||||
current = try await APIClient.shared.updateProduct(
|
||||
@@ -324,32 +363,18 @@ struct ProductDetailView: View {
|
||||
name: name,
|
||||
brand: brand.isEmpty ? nil : brand,
|
||||
packageSize: nil, packageLabel: nil, datePrecision: nil,
|
||||
groupId: nil, categoryId: categoryId,
|
||||
groupId: current.isIndividual ? nil : groupId, categoryId: categoryId,
|
||||
// Einzelstücke tragen den Shop je Stück – nichts ans Modell.
|
||||
shopId: current.isIndividual ? nil : shopId,
|
||||
productUrl: productUrl.isEmpty ? nil : productUrl,
|
||||
fieldValues: fv,
|
||||
minStock: Double(minStock.replacingOccurrences(of: ",", with: "."))
|
||||
.map { $0 * current.articleUnitFactor },
|
||||
minStock: minBase,
|
||||
minStockInPackages: (current.packageSize ?? 0) > 0,
|
||||
minStockUnitId: nil,
|
||||
sendMinStock: true
|
||||
)
|
||||
)
|
||||
fieldValues = Self.stringValues(current.fieldValues)
|
||||
} else {
|
||||
current = try await APIClient.shared.updateProduct(
|
||||
id: current.id,
|
||||
ProductUpdateRequest(
|
||||
name: name,
|
||||
brand: brand.isEmpty ? nil : brand,
|
||||
packageSize: Double(packageSize.replacingOccurrences(of: ",", with: ".")),
|
||||
packageLabel: packageLabel.isEmpty ? nil : packageLabel,
|
||||
datePrecision: datePrecision,
|
||||
groupId: groupId,
|
||||
categoryId: categoryId
|
||||
)
|
||||
)
|
||||
}
|
||||
status = "Gespeichert."
|
||||
} catch {
|
||||
|
||||
Reference in New Issue
Block a user