MHD nur mit Monat und Jahr in Web-UI und iOS-App
Setzt den Backend-Umbau in beiden Oberflaechen um. Die Genauigkeit gilt jeweils fuer den ganzen Einlager-Vorgang und nicht je Charge: Auf einer Packung steht entweder ein Tagesdatum oder nur Monat/Jahr, gemischt kommt das nicht vor. Vorbelegt wird sie aus dem Produkt, laesst sich aber im Vorgang umstellen. Web-UI: Umschalter im Kopf der Chargenliste; das Eingabefeld wechselt zwischen type="date" und type="month". Im Produktformular gibt es das neue Feld "MHD-Angabe" neben der Gebinde-Bezeichnung. Anzeige laeuft ueber den Settings-Context, damit das eingestellte Datumsformat erhalten bleibt und Monatsangaben ueberall als "09/2026" erscheinen (Auslagern, Uebersicht, Chargentabelle). Die Abgelaufen-Warnung prueft bei Monatsangaben gegen den Monatsletzten - sonst haette eine Packung schon am Monatsersten als abgelaufen gegolten. iOS: Auswahl "Tagesdatum / nur Monat/Jahr" im Einlagern-Formular und beim Anlegen eines Artikels. SwiftUI hat keinen DatePicker ohne Tag, deshalb ein eigener MonthYearPicker aus zwei Auswahlfeldern; die Jahresliste reicht zwei Jahre zurueck (bereits abgelaufene Ware) und fuenfzehn nach vorn (Konserven). Die Chargenauswahl beim Auslagern zeigt Monatsangaben ebenfalls ohne Tag. Getestet: Web-Build (vite) und iOS-Geraetebuild laufen fehlerfrei durch, die App ist auf dem iPhone installiert. Das Verhalten in der Oberflaeche ist noch nicht von Hand durchgeklickt. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,62 @@
|
||||
import SwiftUI
|
||||
|
||||
extension Date {
|
||||
/// Erster Tag des Monats - so wird eine Monatsangabe an den Server geschickt.
|
||||
var startOfMonth: Date {
|
||||
let calendar = Calendar.current
|
||||
let parts = calendar.dateComponents([.year, .month], from: self)
|
||||
return calendar.date(from: parts) ?? self
|
||||
}
|
||||
}
|
||||
|
||||
/// Monat und Jahr auswaehlen. SwiftUI kennt keinen DatePicker ohne Tag,
|
||||
/// deshalb zwei schmale Auswahlfelder nebeneinander.
|
||||
struct MonthYearPicker: View {
|
||||
@Binding var date: Date
|
||||
|
||||
private let calendar = Calendar.current
|
||||
private var monthNames: [String] { calendar.monthSymbols }
|
||||
|
||||
private var years: [Int] {
|
||||
let current = calendar.component(.year, from: Date())
|
||||
// Rueckwaerts fuer bereits abgelaufene Ware, vorwaerts fuer Konserven.
|
||||
return Array((current - 2)...(current + 15))
|
||||
}
|
||||
|
||||
private var month: Binding<Int> {
|
||||
Binding(
|
||||
get: { calendar.component(.month, from: date) },
|
||||
set: { date = replacing(month: $0, year: calendar.component(.year, from: date)) }
|
||||
)
|
||||
}
|
||||
|
||||
private var year: Binding<Int> {
|
||||
Binding(
|
||||
get: { calendar.component(.year, from: date) },
|
||||
set: { date = replacing(month: calendar.component(.month, from: date), year: $0) }
|
||||
)
|
||||
}
|
||||
|
||||
private func replacing(month: Int, year: Int) -> Date {
|
||||
calendar.date(from: DateComponents(year: year, month: month, day: 1)) ?? date
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
Text("MHD")
|
||||
Spacer()
|
||||
Picker("Monat", selection: month) {
|
||||
ForEach(1...12, id: \.self) { Text(monthNames[$0 - 1]).tag($0) }
|
||||
}
|
||||
.labelsHidden()
|
||||
Picker("Jahr", selection: year) {
|
||||
ForEach(years, id: \.self) { Text(String($0)).tag($0) }
|
||||
}
|
||||
.labelsHidden()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Mengen-Erfassung nach dem Scan: mehrere Chargen mit eigenem MHD.
|
||||
struct CheckInFormView: View {
|
||||
let product: Product
|
||||
@@ -17,6 +74,9 @@ struct CheckInFormView: View {
|
||||
}
|
||||
|
||||
@State private var lines: [Line] = [Line()]
|
||||
/// Gilt fuer den ganzen Vorgang: Auf einer Packung steht entweder ein
|
||||
/// Tagesdatum oder nur Monat/Jahr - nicht beides gemischt.
|
||||
@State private var precision: String = "day"
|
||||
@State private var unit: String = ""
|
||||
@State private var locationId: Int?
|
||||
@State private var busy = false
|
||||
@@ -63,6 +123,13 @@ struct CheckInFormView: View {
|
||||
}
|
||||
}
|
||||
|
||||
Section("MHD") {
|
||||
Picker("Angabe", selection: $precision) {
|
||||
Text("Tagesdatum").tag("day")
|
||||
Text("nur Monat/Jahr").tag("month")
|
||||
}
|
||||
}
|
||||
|
||||
Section("Chargen") {
|
||||
ForEach($lines) { $line in
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
@@ -84,7 +151,11 @@ struct CheckInFormView: View {
|
||||
}
|
||||
Toggle("MHD angeben", isOn: $line.hasDate)
|
||||
if line.hasDate {
|
||||
DatePicker("MHD", selection: $line.bestBefore, displayedComponents: .date)
|
||||
if precision == "month" {
|
||||
MonthYearPicker(date: $line.bestBefore)
|
||||
} else {
|
||||
DatePicker("MHD", selection: $line.bestBefore, displayedComponents: .date)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 2)
|
||||
@@ -113,6 +184,8 @@ struct CheckInFormView: View {
|
||||
.onAppear {
|
||||
// Gibt es ein Gebinde, ist das die naheliegende Eingabe.
|
||||
unit = packageOptionName ?? product.unitName
|
||||
// Am Produkt hinterlegt, ob dort ueblicherweise nur Monat/Jahr steht.
|
||||
precision = product.datePrecision == "month" ? "month" : "day"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,9 +201,13 @@ struct CheckInFormView: View {
|
||||
let payloadLines: [CheckInLine] = lines.compactMap { line in
|
||||
guard let quantity = Double(line.quantity.replacingOccurrences(of: ",", with: ".")),
|
||||
quantity > 0 else { return nil }
|
||||
// Bei Monatsangabe genuegt der Monatserste - der Server legt daraus
|
||||
// den Monatsletzten und merkt sich die Genauigkeit.
|
||||
let date = precision == "month" ? line.bestBefore.startOfMonth : line.bestBefore
|
||||
return CheckInLine(
|
||||
quantity: quantity,
|
||||
bestBefore: line.hasDate ? formatter.string(from: line.bestBefore) : nil,
|
||||
bestBefore: line.hasDate ? formatter.string(from: date) : nil,
|
||||
bestBeforePrecision: precision,
|
||||
locationId: locationId
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user