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 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-10-02 21:13:43 +02:00
co-authored by Claude Opus 5
parent 25e7f23c6c
commit 892c52e49a
4 changed files with 32 additions and 4 deletions
+1 -1
View File
@@ -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()
+1 -1
View File
@@ -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()
+27 -1
View File
@@ -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"
+3 -1
View File
@@ -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: