App: Obergruppen zuordnen, Mindestbestaende je Ort bearbeiten
Die App konnte bei Gruppen bisher nur umbenennen und Mindestbestaende lesen.
Jetzt gibt es einen vollen Gruppen-Editor (Sources/GroupViews.swift): Name,
Obergruppen als Mehrfachauswahl, Einheit und Mindestbestaende je Ort. Bewusst
eine flache Liste mit "unter: ..." statt TreeMasterView - das Protokoll TreeItem
kennt genau einen Elternteil und wuerde eine Gruppe mit zwei Obergruppen doppelt
zeigen. Die eigene Gruppe und ihre Untergruppen sind in der Auswahl
ausgeschlossen, damit kein Ring entsteht.
Mindestbestaende-Uebersicht ist nach Lagerort gegliedert ("Ueberall" zuerst) und
nicht mehr nur lesend - Gruppenzeilen oeffnen den neuen Editor. Am Artikel
entfaellt das separate Feld "Gesamt": der Bedarf haengt immer an einem Ort, und
"Ueberall" ist einer davon. Das Stammdaten-Speichern schickt min_stock nicht
mehr mit, sonst wuerde es die dort gesetzte Ueberall-Zeile ueberschreiben.
APIClient kann jetzt updateGroup und setGroupLocationMinStock. GroupUpdateRequest
bekommt bewusst KEINEN eigenen Encoder: der synthetisierte laesst nil-Felder weg,
ein handgeschriebener wuerde "name": null senden und den Namen loeschen.
Neue Datei -> xcodegen generate; Projektdatei ist mit committet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -146,30 +146,50 @@ struct Product: Codable, Identifiable, Hashable {
|
||||
}
|
||||
|
||||
/// Mindestbestand eines Produkts/einer Gruppe an einem Lagerort (Anzeige).
|
||||
///
|
||||
/// ``locationId`` nil heisst „Ueberall": egal wo, Hauptsache die Menge ist im
|
||||
/// Haus. Das ersetzt den frueheren separaten Gesamt-Mindestbestand. Mengen in
|
||||
/// Basiseinheiten (g/ml/Stueck).
|
||||
struct LocationMinStock: Codable, Hashable, Identifiable {
|
||||
let locationId: String
|
||||
let locationId: String?
|
||||
let locationName: String?
|
||||
let minStock: Double
|
||||
var id: String { locationId }
|
||||
/// Bestand an diesem Ort (inkl. Unterorte), ebenfalls in Basiseinheiten.
|
||||
var stock: Double? = nil
|
||||
var id: String { locationId ?? UEBERALL_ID }
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case stock
|
||||
case minStock = "min_stock"
|
||||
case locationId = "location_id"
|
||||
case locationName = "location_name"
|
||||
}
|
||||
}
|
||||
|
||||
/// Ein Mindestbestand-Eintrag zum Speichern (Menge in Artikeleinheiten).
|
||||
/// Ein Mindestbestand-Eintrag zum Speichern (Menge in Basiseinheiten).
|
||||
/// ``locationId`` nil = „Ueberall".
|
||||
struct LocationMinStockIn: Codable {
|
||||
let locationId: String
|
||||
let locationId: String?
|
||||
let minStock: Double
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case minStock = "min_stock"
|
||||
case locationId = "location_id"
|
||||
}
|
||||
|
||||
// Den Ort immer senden – auch null, sonst laesst sich „Ueberall" gar nicht
|
||||
// ausdruecken (Swift laesst nil-Optionals sonst weg).
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var c = encoder.container(keyedBy: CodingKeys.self)
|
||||
try c.encode(locationId, forKey: .locationId)
|
||||
try c.encode(minStock, forKey: .minStock)
|
||||
}
|
||||
}
|
||||
|
||||
/// Platzhalter-ID fuer „Ueberall" in Pickern – im JSON ist es schlicht null.
|
||||
let UEBERALL_ID = "__ueberall__"
|
||||
let UEBERALL_NAME = "Überall"
|
||||
|
||||
/// Auswahleintrag fuer Einheiten.
|
||||
///
|
||||
/// Anzeige und uebertragener Wert sind bewusst getrennt: Das Backend kennt
|
||||
@@ -390,6 +410,8 @@ struct GroupShoppingItem: Codable, Identifiable {
|
||||
let deficit: Double
|
||||
let unitName: String
|
||||
let productCount: Int
|
||||
/// Wie viele Untergruppen mitgezaehlt werden (0 = keine).
|
||||
var subgroupCount: Int? = nil
|
||||
|
||||
var id: Int { groupId }
|
||||
|
||||
@@ -399,6 +421,7 @@ struct GroupShoppingItem: Codable, Identifiable {
|
||||
case minStock = "min_stock"
|
||||
case unitName = "unit_name"
|
||||
case productCount = "product_count"
|
||||
case subgroupCount = "subgroup_count"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -637,20 +660,86 @@ struct SettingEntry: Codable {
|
||||
}
|
||||
|
||||
/// Gruppe: fasst Bestaende mehrerer Marken zusammen (z.B. "Mehl").
|
||||
///
|
||||
/// Gruppen bilden einen Graphen, keinen Baum: eine Gruppe darf unter MEHREREN
|
||||
/// Obergruppen haengen („Grillwurst" unter „Wurst" UND unter „Grillgut").
|
||||
/// Deshalb ``parentIds`` als Liste – und deshalb konformiert GroupItem NICHT zu
|
||||
/// ``TreeItem``, das genau einen Elternteil kennt.
|
||||
struct GroupItem: Codable, Identifiable, Hashable {
|
||||
let id: Int
|
||||
let name: String
|
||||
// Mindestbestand-Infos (nur aus /groups befuellt; Picker brauchen sie nicht).
|
||||
var minStock: Double? = nil
|
||||
var minStockUnitId: Int? = nil
|
||||
var minStockUnitName: String? = nil
|
||||
var minStockUnitFactor: Double? = nil
|
||||
var kind: String? = nil
|
||||
var packageSize: Double? = nil
|
||||
var packageLabel: String? = nil
|
||||
var minStockInPackages: Bool? = nil
|
||||
var stock: Double? = nil
|
||||
var locationMinStocks: [LocationMinStock]? = nil
|
||||
var parentIds: [Int]? = nil
|
||||
var childIds: [Int]? = nil
|
||||
/// Artikel inkl. Untergruppen; ``directProductCount`` nur die eigenen.
|
||||
var productCount: Int? = nil
|
||||
var directProductCount: Int? = nil
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id, name, stock
|
||||
case id, name, stock, kind
|
||||
case minStock = "min_stock"
|
||||
case minStockUnitId = "min_stock_unit_id"
|
||||
case minStockUnitName = "min_stock_unit_name"
|
||||
case minStockUnitFactor = "min_stock_unit_factor"
|
||||
case packageSize = "package_size"
|
||||
case packageLabel = "package_label"
|
||||
case minStockInPackages = "min_stock_in_packages"
|
||||
case locationMinStocks = "location_min_stocks"
|
||||
case parentIds = "parent_ids"
|
||||
case childIds = "child_ids"
|
||||
case productCount = "product_count"
|
||||
case directProductCount = "direct_product_count"
|
||||
}
|
||||
|
||||
/// Basiseinheiten je Erfassungseinheit – zum Umrechnen der Mindestbestaende,
|
||||
/// die in Basiseinheiten gespeichert sind.
|
||||
var minFaktor: Double {
|
||||
if minStockInPackages == true, let s = packageSize, s > 0 { return s }
|
||||
return minStockUnitFactor ?? 1
|
||||
}
|
||||
|
||||
/// Beschriftung der Erfassungseinheit („Glas", „Gramm", …).
|
||||
var minEinheit: String {
|
||||
if minStockInPackages == true, (packageSize ?? 0) > 0 {
|
||||
return packageLabel ?? "Packung"
|
||||
}
|
||||
if let n = minStockUnitName { return n }
|
||||
switch kind {
|
||||
case "weight": return "g"
|
||||
case "volume": return "ml"
|
||||
case "count": return "Stück"
|
||||
default: return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Gruppe aendern. Nur mitgeschickte Felder wertet das Backend aus – deshalb
|
||||
/// KEIN eigener Encoder: der synthetisierte laesst nil-Optionals weg
|
||||
/// (encodeIfPresent). Ein handgeschriebener wuerde ``"name": null`` senden und
|
||||
/// damit den Namen loeschen.
|
||||
struct GroupUpdateRequest: Codable {
|
||||
var name: String? = nil
|
||||
var minStock: Double? = nil
|
||||
var minStockUnitId: Int? = nil
|
||||
var minStockInPackages: Bool? = nil
|
||||
var parentIds: [Int]? = nil
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case name
|
||||
case minStock = "min_stock"
|
||||
case minStockUnitId = "min_stock_unit_id"
|
||||
case minStockInPackages = "min_stock_in_packages"
|
||||
case parentIds = "parent_ids"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1283,10 +1372,13 @@ struct NewGroupRequest: Codable {
|
||||
let name: String
|
||||
let minStock: Double?
|
||||
let minStockUnitId: Int?
|
||||
/// Obergruppen (mehrere moeglich) – optional, Standard: keine.
|
||||
var parentIds: [Int]? = nil
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case name
|
||||
case minStock = "min_stock"
|
||||
case minStockUnitId = "min_stock_unit_id"
|
||||
case parentIds = "parent_ids"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user