From 892c52e49a59ba8da1fa662c558e175b1ff83c01 Mon Sep 17 00:00:00 2001 From: Scarriffle Date: Fri, 2 Oct 2026 21:13:43 +0200 Subject: [PATCH] App: /expiring kann jetzt ohne MHD kommen - das hat sie nicht ausgehalten Beim Zusammenstellen der Testliste fuer den naechsten iOS-Build aufgefallen, bevor es jemandem begegnet ist: Seit den angebrochenen Packungen liefert /expiring auch Chargen OHNE aufgedrucktes MHD - eine geoeffnete Packung hat nur ein Verbrauchsdatum, und genau die gehoert in diese Liste. `best_before` ist dort seither null. Im Swift-Modell stand `let bestBefore: String`, nicht optional. Eine einzige solche Charge haette das Entschluesseln der GANZEN Antwort scheitern lassen: keine Ablaufliste, keine Ablaufkarte - und keine Erinnerungen, denn der Planer laedt dieselbe Antwort. Gegen den laufenden Server geprueft: Eine angebrochene Packung ohne MHD kommt als {"best_before": null, "consume_by": "2026-10-05", "warn_date": "2026-10-05", "warn_source": "consume_by"}. Mitgenommen, weil es ohnehin an derselben Stelle sitzt: Das Modell kennt jetzt opened_on, consume_by, warn_date und warn_source. Die Erinnerungen haengen am Warndatum statt am MHD - eine angebrochene Packung ohne aufgedrucktes Datum braucht sie am dringendsten und bekam bisher keine. Liste und Dashboard-Karte schreiben "verbrauchen bis 05.10." statt "MHD ohne MHD", denn das sind zwei verschiedene Aussagen. Nicht kompiliert: Auf dieser Maschine gibt es kein Xcode. Geprueft sind Klammerbalance, Anfuehrungszeichen, dass item.bestBefore nirgends mehr nicht-optional benutzt wird, dass ExpiringItem nirgends von Hand erzeugt wird (nur entschluesselt), und der Abgleich jedes Feldnamens gegen eine echte Serverantwort. Der Build in Xcode bleibt die Probe. Co-Authored-By: Claude Opus 5 --- ios/Sources/DashboardCards.swift | 2 +- ios/Sources/ListViews.swift | 2 +- ios/Sources/Models.swift | 28 ++++++++++++++++++++++++- ios/Sources/NotificationScheduler.swift | 4 +++- 4 files changed, 32 insertions(+), 4 deletions(-) diff --git a/ios/Sources/DashboardCards.swift b/ios/Sources/DashboardCards.swift index 72569ca..42ddb4e 100644 --- a/ios/Sources/DashboardCards.swift +++ b/ios/Sources/DashboardCards.swift @@ -291,7 +291,7 @@ struct ExpiryCard: View { HStack { VStack(alignment: .leading, spacing: 1) { Text(item.productName).font(.callout) - Text("MHD \(display.formatBestBefore(item.bestBefore, precision: item.bestBeforePrecision))") + Text("\(item.fristLabel) \(display.formatBestBefore(item.warnDate, precision: item.fristPrecision))") .font(.caption2).foregroundStyle(.secondary) } Spacer() diff --git a/ios/Sources/ListViews.swift b/ios/Sources/ListViews.swift index 535cd63..52ca28f 100644 --- a/ios/Sources/ListViews.swift +++ b/ios/Sources/ListViews.swift @@ -146,7 +146,7 @@ struct ExpiringView: View { HStack { VStack(alignment: .leading, spacing: 2) { Text(item.productName) - Text("MHD \(display.formatBestBefore(item.bestBefore, precision: item.bestBeforePrecision)) · \(articleAmount(item))") + Text("\(item.fristLabel) \(display.formatBestBefore(item.warnDate, precision: item.fristPrecision)) · \(articleAmount(item))") .font(.caption).foregroundStyle(.secondary) } Spacer() diff --git a/ios/Sources/Models.swift b/ios/Sources/Models.swift index 9cc84a3..3e0948e 100644 --- a/ios/Sources/Models.swift +++ b/ios/Sources/Models.swift @@ -617,8 +617,19 @@ struct ExpiringItem: Codable, Identifiable { let productName: String let quantity: Double let baseUnit: String - let bestBefore: String + /// Darf fehlen! Eine angebrochene Packung ohne aufgedrucktes MHD hat nur ein + /// Verbrauchsdatum - und genau die gehoert in diese Liste. Stand hier frueher + /// ein nicht-optionaler String, scheiterte das Entschluesseln der GANZEN + /// Liste an einer einzigen solchen Charge, samt Erinnerungen. + let bestBefore: String? let bestBeforePrecision: String? + let openedOn: String? + let consumeBy: String? + /// Das Datum, nach dem gewarnt wird: das fruehere von MHD und + /// Verbrauchsdatum. Immer gesetzt - der Server rechnet es aus. + let warnDate: String + /// "best_before" oder "consume_by" - sagt, welches der beiden Daten gewann. + let warnSource: String? let daysLeft: Int let packageSize: Double? let packageLabel: String? @@ -627,6 +638,17 @@ struct ExpiringItem: Codable, Identifiable { var id: Int { lotId } + /// Ist die Packung angebrochen und draengt deshalb? + var istAngebrochen: Bool { warnSource == "consume_by" } + + /// Beschriftung vor dem Datum. "MHD 05.10." und "verbrauchen bis 05.10." + /// sind zwei verschiedene Aussagen, und die Liste zeigt beide nebeneinander. + var fristLabel: String { istAngebrochen ? "verbrauchen bis" : "MHD" } + + /// Genauigkeit des angezeigten Datums. Ein Verbrauchsdatum ist immer + /// taggenau, auch wenn auf der Packung nur ein Monat stand. + var fristPrecision: String? { istAngebrochen ? "day" : bestBeforePrecision } + /// Restlaufzeit als kurzer Text. „0 Tage" ergibt keinen Sinn - am Ablauftag /// selbst steht „heute". Karte und Liste nutzen dieselbe Formulierung. var restlaufzeitText: String { @@ -644,6 +666,10 @@ struct ExpiringItem: Codable, Identifiable { case baseUnit = "base_unit" case bestBefore = "best_before" case bestBeforePrecision = "best_before_precision" + case openedOn = "opened_on" + case consumeBy = "consume_by" + case warnDate = "warn_date" + case warnSource = "warn_source" case daysLeft = "days_left" case packageSize = "package_size" case packageLabel = "package_label" diff --git a/ios/Sources/NotificationScheduler.swift b/ios/Sources/NotificationScheduler.swift index 5908076..f72d63c 100644 --- a/ios/Sources/NotificationScheduler.swift +++ b/ios/Sources/NotificationScheduler.swift @@ -75,7 +75,9 @@ enum ExpiryPlanner { } for item in snapshot.expiring { - guard let mhd = parseTag(item.bestBefore, calendar: calendar) else { continue } + // Das Warndatum, nicht das MHD: Eine angebrochene Packung ohne + // aufgedrucktes Datum braucht die Erinnerung am dringendsten. + guard let mhd = parseTag(item.warnDate, calendar: calendar) else { continue } let mhdTag = calendar.startOfDay(for: mhd) // Welche Regeln gelten fuer dieses Produkt? Kategorie ersetzt Allgemeine: