import Foundation // Entspricht den Schemas des Backends (backend/app/schemas.py). struct TokenResponse: Codable { let accessToken: String let role: String let username: String enum CodingKeys: String, CodingKey { case accessToken = "access_token" case role, username } } struct MeResponse: Codable { let id: Int let username: String let role: String var isAdmin: Bool { role == "admin" } } struct Unit: Codable, Identifiable, Hashable { let id: Int let name: String let kind: String let factor: Double /// Eingebaute Einheiten lassen sich umbenennen, aber nicht loeschen. let isBuiltin: Bool enum CodingKeys: String, CodingKey { case id, name, kind, factor case isBuiltin = "is_builtin" } } struct BarcodeEntry: Codable, Identifiable, Hashable { let id: Int let code: String let note: String? } struct Product: Codable, Identifiable, Hashable { let id: Int let barcode: String? let name: String let brand: String? let imageUrl: String? let baseUnit: String let packageSize: Double? let packageLabel: String? let groupId: Int? let minStock: Double? let stock: Double let expiredCount: Int let kind: String let unitName: String let unitFactor: Double let barcodes: [BarcodeEntry] /// Welche MHD-Genauigkeit bei diesem Produkt ueblich ist ("day"/"month"). let datePrecision: String? /// Kategorie: reine Ordnungshilfe fuer die Artikelliste (nicht die Gruppe). let categoryId: Int? let categoryName: String? /// Mindestbestand in der erfassten Einheit, fuer die Anzeige. let minStockDisplay: Double? let minStockUnitLabel: String? /// Verwaltungsart aus der Kategorie: "food" (Chargen+MHD) oder "object" (Menge je Ort). let tracking: String? /// Nur fuer Gegenstaende: Bezugsquelle und Onlineshop-Link. let shopId: Int? let shopName: String? let productUrl: String? /// Selbst definierte Feldwerte: {feld_id (als Text): Wert}. let fieldValues: [String: String?]? /// Gegenstand als Einzelstücke (Items mit UID/QR) statt als Menge geführt. let individual: Bool? /// Mindestbestand je Lagerort (zusaetzlich zum globalen Mindestbestand). let locationMinStocks: [LocationMinStock]? enum CodingKeys: String, CodingKey { case id, barcode, name, brand, stock, kind, barcodes, tracking, individual case locationMinStocks = "location_min_stocks" case datePrecision = "date_precision" case categoryId = "category_id" case categoryName = "category_name" case minStockDisplay = "min_stock_display" case minStockUnitLabel = "min_stock_unit_label" case imageUrl = "image_url" case baseUnit = "base_unit" case packageSize = "package_size" case packageLabel = "package_label" case groupId = "group_id" case minStock = "min_stock" case expiredCount = "expired_count" case unitName = "unit_name" case unitFactor = "unit_factor" case shopId = "shop_id" case shopName = "shop_name" case productUrl = "product_url" case fieldValues = "field_values" } /// Gegenstand (Menge je Lagerort) statt Lebensmittel (Chargen/MHD)? var isObject: Bool { tracking == "object" } /// Gegenstand, der als Einzelstücke (UID/QR) geführt wird? var isIndividual: Bool { individual == true } /// Bezeichnung der Artikeleinheit (Gebinde, sonst Produkteinheit). var articleUnitLabel: String { if let size = packageSize, size > 0 { return packageLabel ?? "Packung" } return unitName } /// Faktor, um Basiseinheiten in Artikeleinheiten umzurechnen. var articleUnitFactor: Double { if let size = packageSize, size > 0 { return size } return unitFactor == 0 ? 1 : unitFactor } var stockInArticleUnits: Double { stock / articleUnitFactor } /// Wie es um den Mindestbestand steht. Beides in Basiseinheiten verglichen. enum StockLevel { case none, ok, close, below } var stockLevel: StockLevel { guard let minimum = minStock, minimum > 0 else { return .none } if stock < minimum { return .below } // Weniger als ein Viertel Luft: bald nachkaufen. return stock < minimum * 1.25 ? .close : .ok } } /// Mindestbestand eines Produkts/einer Gruppe an einem Lagerort (Anzeige). struct LocationMinStock: Codable, Hashable, Identifiable { let locationId: String let locationName: String? let minStock: Double var id: String { locationId } enum CodingKeys: String, CodingKey { case minStock = "min_stock" case locationId = "location_id" case locationName = "location_name" } } /// Ein Mindestbestand-Eintrag zum Speichern (Menge in Artikeleinheiten). struct LocationMinStockIn: Codable { let locationId: String let minStock: Double enum CodingKeys: String, CodingKey { case minStock = "min_stock" case locationId = "location_id" } } /// Auswahleintrag fuer Einheiten. /// /// Anzeige und uebertragener Wert sind bewusst getrennt: Das Backend kennt /// fuer das Gebinde nur das Schluesselwort "package" (siehe /// services/conversion.py). Die Bezeichnung "Glas" oder "Dose" ist reine /// Anzeige - wird sie mitgeschickt, antwortet der Server /// "Unbekannte Einheit: Glas". struct UnitOption: Hashable, Identifiable { let value: String let label: String var id: String { value } static let packageValue = "package" } struct LookupResult: Codable { let found: Bool let existingProduct: Product? let suggestion: Suggestion? /// Gruppe: nur gesetzt, wenn der Code dort ausdruecklich hinterlegt ist. let groupId: Int? let groupName: String? /// Kategorie: aus der Open-Food-Facts-Einordnung vorgeschlagen. let categoryId: Int? let categoryName: String? enum CodingKeys: String, CodingKey { case found, suggestion case existingProduct = "existing_product" case groupId = "group_id" case groupName = "group_name" case categoryId = "category_id" case categoryName = "category_name" } struct Suggestion: Codable { let barcode: String? let name: String let brand: String? let imageUrl: String? let baseUnit: String? let packageSize: Double? let quantityText: String? enum CodingKeys: String, CodingKey { case barcode, name, brand case imageUrl = "image_url" case baseUnit = "base_unit" case packageSize = "package_size" case quantityText = "quantity_text" } } } struct Lot: Codable, Identifiable, Hashable { let id: Int let productId: Int let quantity: Double let bestBefore: String? let bestBeforePrecision: String? let locationId: String? enum CodingKeys: String, CodingKey { case id, quantity case productId = "product_id" case bestBefore = "best_before" case bestBeforePrecision = "best_before_precision" case locationId = "location_id" } } struct StorageLocation: Codable, Identifiable, Hashable { let id: String let name: String let parentId: String? enum CodingKeys: String, CodingKey { case id, name case parentId = "parent_id" } } struct CheckInLine: Codable { let quantity: Double let bestBefore: String? let bestBeforePrecision: String let locationId: String? enum CodingKeys: String, CodingKey { case quantity case bestBefore = "best_before" case bestBeforePrecision = "best_before_precision" case locationId = "location_id" } } struct BatchCheckInRequest: Codable { let productId: Int let unit: String let lines: [CheckInLine] enum CodingKeys: String, CodingKey { case unit, lines case productId = "product_id" } } struct CheckOutRequest: Codable { let productId: Int let quantity: Double let unit: String let lotId: Int? enum CodingKeys: String, CodingKey { case quantity, unit case productId = "product_id" case lotId = "lot_id" } } struct StockResponse: Codable { let productStock: Double enum CodingKeys: String, CodingKey { case productStock = "product_stock" } } struct NewProductRequest: Codable { let barcode: String? let name: String let brand: String? let imageUrl: String? let baseUnit: String let packageSize: Double? let packageLabel: String? let datePrecision: String let groupId: Int? let categoryId: Int? var individual: Bool? = nil enum CodingKeys: String, CodingKey { case barcode, name, brand, individual case imageUrl = "image_url" case baseUnit = "base_unit" case packageSize = "package_size" case packageLabel = "package_label" case datePrecision = "date_precision" case groupId = "group_id" case categoryId = "category_id" } } // MARK: - Listen (Einkaufsliste, bald ablaufend) struct ShoppingItem: Codable, Identifiable { let productId: Int let name: String let baseUnit: String let packageSize: Double? let stock: Double let minStock: Double let deficit: Double var id: Int { productId } enum CodingKeys: String, CodingKey { case name, stock, deficit case productId = "product_id" case baseUnit = "base_unit" case packageSize = "package_size" case minStock = "min_stock" } } struct GroupShoppingItem: Codable, Identifiable { let groupId: Int let name: String let stock: Double let minStock: Double let deficit: Double let unitName: String let productCount: Int var id: Int { groupId } enum CodingKeys: String, CodingKey { case name, stock, deficit case groupId = "group_id" case minStock = "min_stock" case unitName = "unit_name" case productCount = "product_count" } } // MARK: - Bedarfe je Lagerort struct LocationNeedProduct: Codable, Identifiable { let productId: Int let name: String let unitLabel: String let stock: Double let minStock: Double let deficit: Double var id: Int { productId } enum CodingKeys: String, CodingKey { case name, stock, deficit case productId = "product_id" case unitLabel = "unit_label" case minStock = "min_stock" } } struct LocationNeedGroup: Codable, Identifiable { let groupId: Int let name: String let unitName: String let stock: Double let minStock: Double let deficit: Double var id: Int { groupId } enum CodingKeys: String, CodingKey { case name, stock, deficit case groupId = "group_id" case unitName = "unit_name" case minStock = "min_stock" } } struct LocationNeeds: Codable, Identifiable { let locationId: String let locationName: String let products: [LocationNeedProduct] let groups: [LocationNeedGroup] var id: String { locationId } enum CodingKeys: String, CodingKey { case products, groups case locationId = "location_id" case locationName = "location_name" } } struct ExpiringItem: Codable, Identifiable { let lotId: Int let productId: Int let productName: String let quantity: Double let baseUnit: String let bestBefore: String let bestBeforePrecision: String? let daysLeft: Int let packageSize: Double? let packageLabel: String? let unitName: String let unitFactor: Double var id: Int { lotId } enum CodingKeys: String, CodingKey { case quantity case lotId = "lot_id" case productId = "product_id" case productName = "product_name" case baseUnit = "base_unit" case bestBefore = "best_before" case bestBeforePrecision = "best_before_precision" case daysLeft = "days_left" case packageSize = "package_size" case packageLabel = "package_label" case unitName = "unit_name" case unitFactor = "unit_factor" } } // MARK: - Aenderungen /// Die Felder, die das Produktformular der App besitzt (PATCH). /// /// Bewusst mit eigener Kodierung: Swift laesst `nil`-Optionals sonst einfach /// weg, und das Backend wertet nur mitgeschickte Felder aus. Leeren waere damit /// unmoeglich - "keine Kategorie" oder eine geloeschte Marke wuerden stumm /// verpuffen. Felder, die die App nicht bearbeitet (etwa der Mindestbestand), /// stehen deshalb gar nicht erst hier drin. struct ProductUpdateRequest: Encodable { var name: String? var brand: String? var packageSize: Double? var packageLabel: String? var datePrecision: String? var groupId: Int? var categoryId: Int? // Nur fuer Gegenstaende (Standard nil = nicht mitschicken bei fieldValues). var shopId: Int? = nil var productUrl: String? = nil var fieldValues: [String: String?]? = nil enum CodingKeys: String, CodingKey { case name, brand case packageSize = "package_size" case packageLabel = "package_label" case datePrecision = "date_precision" case groupId = "group_id" case categoryId = "category_id" case shopId = "shop_id" case productUrl = "product_url" case fieldValues = "field_values" } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(name, forKey: .name) try container.encode(brand, forKey: .brand) try container.encode(packageSize, forKey: .packageSize) try container.encode(packageLabel, forKey: .packageLabel) try container.encode(datePrecision, forKey: .datePrecision) try container.encode(groupId, forKey: .groupId) try container.encode(categoryId, forKey: .categoryId) try container.encode(shopId, forKey: .shopId) try container.encode(productUrl, forKey: .productUrl) // Feldwerte nur senden, wenn gesetzt – sonst nichts an den Feldern ändern. if let fieldValues { try container.encode(fieldValues, forKey: .fieldValues) } } } struct LotUpdateRequest: Codable { var quantity: Double? var bestBefore: String? var bestBeforePrecision: String? enum CodingKeys: String, CodingKey { case quantity case bestBefore = "best_before" case bestBeforePrecision = "best_before_precision" } } /// Ein Eintrag aus /settings (Schluessel/Wert). struct SettingEntry: Codable { let key: String let value: String } /// Gruppe: fasst Bestaende mehrerer Marken zusammen (z.B. "Mehl"). struct GroupItem: Codable, Identifiable, Hashable { let id: Int let name: String } /// Kategorie: ordnet die Artikelliste und bestimmt die Verwaltungsart /// ("food"/"object"), verschachtelbar (Suesswaren -> Schokolade). struct CategoryItem: Codable, Identifiable, Hashable { let id: Int let name: String let parentId: Int? let tracking: String? enum CodingKeys: String, CodingKey { case id, name, tracking case parentId = "parent_id" } var isObject: Bool { tracking == "object" } } // MARK: - Hierarchische Stammdaten /// Gemeinsame Form baumartiger Stammdaten (Kategorien, Lagerorte): eine flache /// Liste mit Eltern-Verweis, aus der sich der Baum aufbauen lässt. protocol TreeItem: Identifiable { var name: String { get } var parentId: ID? { get } } extension CategoryItem: TreeItem {} extension StorageLocation: TreeItem {} /// Shop / Bezugsquelle fuer Gegenstaende ("gekauft bei"). struct ShopItem: Codable, Identifiable, Hashable { let id: Int let name: String let website: String? let productCount: Int? enum CodingKeys: String, CodingKey { case id, name, website case productCount = "product_count" } } /// Selbst definiertes Feld einer Kategorie (inkl. der geerbten). struct FieldDefinition: Codable, Identifiable, Hashable { let id: Int let categoryId: Int let label: String let key: String let fieldType: String let unit: String? let options: [String] let required: Bool let position: Int let isBuiltin: Bool let inherited: Bool enum CodingKeys: String, CodingKey { case id, label, key, unit, options, required, position, inherited case categoryId = "category_id" case fieldType = "field_type" case isBuiltin = "is_builtin" } } // MARK: - Gegenstaende: Bestandsbuchungen struct ObjectCheckInRequest: Codable { let productId: Int let quantity: Double let unit: String let locationId: String? enum CodingKeys: String, CodingKey { case quantity, unit case productId = "product_id" case locationId = "location_id" } } struct RelocateRequest: Codable { let productId: Int let quantity: Double let fromLocationId: String? let toLocationId: String? enum CodingKeys: String, CodingKey { case quantity case productId = "product_id" case fromLocationId = "from_location_id" case toLocationId = "to_location_id" } } struct RemoveRequest: Codable { let productId: Int let quantity: Double let locationId: String? let reason: String let note: String? enum CodingKeys: String, CodingKey { case quantity, reason, note case productId = "product_id" case locationId = "location_id" } } struct RemovalStat: Codable, Identifiable, Hashable { let reason: String let quantity: Double let count: Int var id: String { reason } } struct RemovalHistoryItem: Codable, Identifiable, Hashable { let reason: String let quantity: Double let locationId: String? let locationName: String? let note: String? let username: String? let createdAt: String var id: String { "\(reason)-\(createdAt)-\(quantity)" } enum CodingKeys: String, CodingKey { case reason, quantity, note, username case locationId = "location_id" case locationName = "location_name" case createdAt = "created_at" } } struct RemovalSummary: Codable { let stats: [RemovalStat] let history: [RemovalHistoryItem] } /// Anzeige-Bezeichnungen der Entnahmegruende. enum RemovalReasons { static let all: [(value: String, label: String)] = [ ("broken", "kaputt"), ("lost", "verloren"), ("given_away", "verschenkt"), ("sold", "verkauft"), ("used_up", "aufgebraucht"), ("other", "sonstiges"), ] static func label(_ value: String) -> String { all.first { $0.value == value }?.label ?? value } } // MARK: - Einzelstücke (Items mit UID/QR) struct ItemDocument: Codable, Identifiable, Hashable { let id: Int let filename: String let contentType: String let uploadedAt: String enum CodingKeys: String, CodingKey { case id, filename case contentType = "content_type" case uploadedAt = "uploaded_at" } } struct Item: Codable, Identifiable, Hashable { let id: Int let uid: String let productId: Int let locationId: String? let locationName: String? let shopId: Int? let shopName: String? let acquiredOn: String? // "yyyy-MM-dd" let warrantyUntil: String? let note: String? let priceCents: Int? let currency: String? let createdAt: String let productName: String? let productBrand: String? let documents: [ItemDocument] enum CodingKeys: String, CodingKey { case id, uid, note, currency, documents case productId = "product_id" case locationId = "location_id" case locationName = "location_name" case shopId = "shop_id" case shopName = "shop_name" case acquiredOn = "acquired_on" case warrantyUntil = "warranty_until" case priceCents = "price_cents" case createdAt = "created_at" case productName = "product_name" case productBrand = "product_brand" } } /// Antwort nach dem Beleg-Upload – mit den aus dem PDF geschätzten Werten. /// Aus einem Beleg-PDF geschätzte Werte (Analyse ohne Speichern). struct DocumentSuggestions: Codable { var suggestedWarrantyUntil: String? = nil var suggestedPriceCents: Int? = nil var suggestedPriceCandidates: [Int] = [] var suggestedAcquiredOn: String? = nil var suggestedShopId: Int? = nil var suggestedShopName: String? = nil enum CodingKeys: String, CodingKey { case suggestedWarrantyUntil = "suggested_warranty_until" case suggestedPriceCents = "suggested_price_cents" case suggestedPriceCandidates = "suggested_price_candidates" case suggestedAcquiredOn = "suggested_acquired_on" case suggestedShopId = "suggested_shop_id" case suggestedShopName = "suggested_shop_name" } init() {} init(from decoder: Decoder) throws { let c = try decoder.container(keyedBy: CodingKeys.self) suggestedWarrantyUntil = try c.decodeIfPresent(String.self, forKey: .suggestedWarrantyUntil) suggestedPriceCents = try c.decodeIfPresent(Int.self, forKey: .suggestedPriceCents) suggestedPriceCandidates = try c.decodeIfPresent([Int].self, forKey: .suggestedPriceCandidates) ?? [] suggestedAcquiredOn = try c.decodeIfPresent(String.self, forKey: .suggestedAcquiredOn) suggestedShopId = try c.decodeIfPresent(Int.self, forKey: .suggestedShopId) suggestedShopName = try c.decodeIfPresent(String.self, forKey: .suggestedShopName) } } struct ItemDocumentUpload: Codable { let id: Int let suggestions: DocumentSuggestions enum IdKey: String, CodingKey { case id } init(from decoder: Decoder) throws { id = try decoder.container(keyedBy: IdKey.self).decode(Int.self, forKey: .id) suggestions = try DocumentSuggestions(from: decoder) } } struct ItemCreateRequest: Codable { let count: Int let locationId: String? let shopId: Int? let acquiredOn: String? let warrantyUntil: String? let note: String? var priceCents: Int? = nil var currency: String? = nil enum CodingKeys: String, CodingKey { case count, note, currency case locationId = "location_id" case shopId = "shop_id" case acquiredOn = "acquired_on" case warrantyUntil = "warranty_until" case priceCents = "price_cents" } } struct ItemUpdateRequest: Encodable { var locationId: String? var shopId: Int? var acquiredOn: String? var warrantyUntil: String? var note: String? var priceCents: Int? var currency: String? enum CodingKeys: String, CodingKey { case note, currency case locationId = "location_id" case shopId = "shop_id" case acquiredOn = "acquired_on" case warrantyUntil = "warranty_until" case priceCents = "price_cents" } // Immer alle Felder senden (auch null), damit sich ein Feld leeren lässt. func encode(to encoder: Encoder) throws { var c = encoder.container(keyedBy: CodingKeys.self) try c.encode(locationId, forKey: .locationId) try c.encode(shopId, forKey: .shopId) try c.encode(acquiredOn, forKey: .acquiredOn) try c.encode(warrantyUntil, forKey: .warrantyUntil) try c.encode(note, forKey: .note) try c.encode(priceCents, forKey: .priceCents) try c.encode(currency, forKey: .currency) } } struct ItemRemoveRequest: Codable { let reason: String let note: String? } // MARK: - Uebersicht und Verlauf /// Kennzahlen der Startseite (GET /dashboard/stats). struct DashboardStats: Codable { let productsInStock: Int let articleUnits: Double let expiringSoon: Int let expired: Int let shoppingItems: Int let productsTotal: Int enum CodingKeys: String, CodingKey { case productsInStock = "products_in_stock" case articleUnits = "article_units" case expiringSoon = "expiring_soon" case expired case shoppingItems = "shopping_items" case productsTotal = "products_total" } } /// Ein Eintrag im Bewegungsverlauf (GET /movements). struct Movement: Codable, Identifiable, Hashable { let id: Int let productId: Int let productName: String /// "in" oder "out" let type: String /// in Basiseinheiten let quantity: Double let baseUnit: String let unitUsed: String let username: String? let note: String? let createdAt: String let packageSize: Double? let packageLabel: String? let unitName: String let unitFactor: Double enum CodingKeys: String, CodingKey { case id, type, quantity, username, note case productId = "product_id" case productName = "product_name" case baseUnit = "base_unit" case unitUsed = "unit_used" case createdAt = "created_at" case packageSize = "package_size" case packageLabel = "package_label" case unitName = "unit_name" case unitFactor = "unit_factor" } var isIncoming: Bool { type == "in" } } // MARK: - Dashboards /// Eine Karte auf einem Dashboard. /// /// `i` ist die Kennung **dieser** Karte, `type` ihre Art - erst dadurch kann /// dieselbe Art mehrfach auf einem Dashboard liegen. `props` traegt, was nur /// diese eine Karte angeht (etwa welcher Artikel gemeint ist). struct DashboardCard: Codable, Identifiable, Hashable { let i: String let type: String let x: Int let y: Int let w: Int let h: Int let tage: Int? let props: CardProps? var id: String { i } /// Nur die Felder, die die App auswertet. Die Rasterangaben kommen aus der /// Web-Oberflaeche; hier bestimmen sie allein die Reihenfolge. struct CardProps: Codable, Hashable { let productId: Int? enum CodingKeys: String, CodingKey { case productId = "product_id" } } } struct Dashboard: Codable, Identifiable, Hashable { let id: Int let name: String let position: Int let layout: [DashboardCard] } struct DashboardList: Codable { let dashboards: [Dashboard] let source: String let enforced: Bool let hasDefault: Bool enum CodingKeys: String, CodingKey { case dashboards, source, enforced case hasDefault = "has_default" } } // MARK: - Diagrammdaten (Übersicht) /// Artikeleinheiten je Ablaufzustand (GET /dashboard/expiry-split). struct ExpirySplit: Codable { let ok: Double let soon: Double let expired: Double let noDate: Double enum CodingKeys: String, CodingKey { case ok, soon, expired case noDate = "no_date" } } /// Anteil einer Kategorie am Bestand (GET /dashboard/by-category). struct CategoryShare: Codable, Identifiable { let categoryId: Int? let name: String let articleUnits: Double let ok: Double let soon: Double let expired: Double let noDate: Double var id: Int { categoryId ?? -1 } enum CodingKeys: String, CodingKey { case name, ok, soon, expired case categoryId = "category_id" case articleUnits = "article_units" case noDate = "no_date" } } /// Ein Punkt des Bestandsverlaufs (GET /dashboard/timeline). struct TimelinePoint: Codable, Identifiable { let at: String let articleUnits: Double var id: String { at } enum CodingKeys: String, CodingKey { case at case articleUnits = "article_units" } } /// Ein- und Auslagerungen je Zeitpunkt (GET /dashboard/activity). struct ActivityPoint: Codable, Identifiable { let at: String let checkedIn: Int let checkedOut: Int var id: String { at } enum CodingKeys: String, CodingKey { case at case checkedIn = "checked_in" case checkedOut = "checked_out" } } // MARK: - Stammdaten /// Gebinde (Packung, Glas, ...) mit Einzahl und Mehrzahl. struct PackageType: Codable, Identifiable, Hashable { let id: Int let singular: String let plural: String let isBuiltin: Bool enum CodingKeys: String, CodingKey { case id, singular, plural case isBuiltin = "is_builtin" } } struct NewLocationRequest: Codable { let name: String let parentId: String? enum CodingKeys: String, CodingKey { case name case parentId = "parent_id" } } /// Lagerort umbenennen und/oder umhängen. `parentId` wird bewusst immer gesendet /// (auch `null` = oberste Ebene), damit „auf oberste Ebene holen" ankommt. struct LocationUpdateRequest: Codable { let name: String let parentId: String? enum CodingKeys: String, CodingKey { case name case parentId = "parent_id" } func encode(to encoder: Encoder) throws { var c = encoder.container(keyedBy: CodingKeys.self) try c.encode(name, forKey: .name) try c.encode(parentId, forKey: .parentId) // encodet null bei nil } } /// Eigenes Feld einer Kategorie anlegen. `unit`/`options` werden immer /// mitgeschickt; der Server ignoriert sie bei unpassendem Typ. struct FieldDefinitionCreate: Codable { let categoryId: Int let label: String let fieldType: String let unit: String let options: [String] let required: Bool enum CodingKeys: String, CodingKey { case label, unit, options, required case categoryId = "category_id" case fieldType = "field_type" } } struct FieldDefinitionUpdate: Codable { let label: String let fieldType: String let unit: String let options: [String] let required: Bool enum CodingKeys: String, CodingKey { case label, unit, options, required case fieldType = "field_type" } } struct NewUnitRequest: Codable { let name: String let kind: String let factor: Double } struct NewPackageTypeRequest: Codable { let singular: String let plural: String } struct NewCategoryRequest: Codable { let name: String let parentId: Int? var tracking: String? = nil enum CodingKeys: String, CodingKey { case name, tracking case parentId = "parent_id" } } struct CategoryUpdateRequest: Codable { let name: String? let tracking: String? } struct NewShopRequest: Codable { let name: String let website: String? } struct ShopUpdateRequest: Codable { let name: String? let website: String? } /// Umbenennen von Stammdaten. Ein einzelnes Feld reicht, weil das Backend nur /// mitgeschickte Schluessel auswertet. struct RenameRequest: Codable { let name: String } struct PackageTypeUpdateRequest: Codable { let singular: String let plural: String } /// Neue Gruppe anlegen (nur fuer Administratoren). struct NewGroupRequest: Codable { let name: String let minStock: Double? let minStockUnitId: Int? enum CodingKeys: String, CodingKey { case name case minStock = "min_stock" case minStockUnitId = "min_stock_unit_id" } }