App: Einheiten-Felder als zusammenhaengender Block

Dieselbe Umstellung wie im Web. Der Abschnitt "Artikel" traegt nur noch Name
und Marke; alles zur Einheit steht unter "Einheit und Menge" und liest sich als
Satz ("1 Packung sind 3 Stueck"). Das Gebinde heisst jetzt auch so statt
"Bezeichnung" und steht vor der Packungsgroesse, weil die Zeile darunter es
beim Namen nennt.

Die Umrechnung heisst nach dem, was sie liefert (Gewicht / Volumen /
Stueckzahl), beginnt mit einer einzigen Auswahl und zeigt den Satz erst danach -
mit passendem Verb und Ein-/Mehrzahl. Die linke Zahl wird beim Waehlen der
Zielart einmalig mit der Packungsgroesse vorbelegt.

Das Anlege-Formular kannte die Umrechnung bisher gar nicht; wer in der App
einen Artikel anlegte, musste ihn erst speichern und wieder oeffnen. Es hat
jetzt denselben Abschnitt und uebernimmt den Vorschlag aus dem Barcode-Lookup
("3 x 80 g").

Reine Oberflaeche - 226 Backend-Tests unveraendert gruen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-08-16 09:21:42 +02:00
parent 37bc7663af
commit 158175f693
3 changed files with 160 additions and 16 deletions

View File

@@ -122,38 +122,44 @@ struct ProductDetailView: View {
Section("Artikel") {
LabeledField(label: "Name", text: $name)
LabeledField(label: "Marke", text: $brand)
if current.foodLike {
QuantityField(label: "Packungsgröße", text: $packageSize,
suffix: display.baseUnitLabel(current.baseUnit))
Picker("Bezeichnung", selection: $packageLabel) {
}
// Alles zur Einheit steht zusammen und liest sich als Satz: das
// Gebinde zuerst, weil die Zeile darunter es beim Namen nennt.
if current.foodLike {
Section("Einheit und Menge") {
Picker("Gebinde", selection: $packageLabel) {
ForEach(labels, id: \.self) { Text($0.isEmpty ? "Packung (Standard)" : $0).tag($0) }
}
QuantityField(label: "1 \(gebindeName) sind", text: $packageSize,
suffix: display.baseUnitLabel(current.baseUnit))
Picker("MHD-Angabe", selection: $datePrecision) {
Text("Tagesdatum").tag("day")
Text("nur Monat/Jahr").tag("month")
}
}
}
// Zweiteinheit: dieselbe Ware in der ANDEREN Art lesbar machen.
// Damit zaehlt der Artikel auch in Gruppen der anderen Art und
// laesst sich darin ein- und auslagern. Das Gebinde bleibt frei.
if current.foodLike {
// Umrechnung in die ANDERE Art: 3 Stück wiegen 250 g". Damit
// zaehlt der Artikel auch in Gruppen, die in dieser Einheit
// rechnen. Das Gebinde bleibt davon unberuehrt.
Section {
QuantityField(label: "Menge", text: $zweitAnzahl,
suffix: display.baseUnitLabel(current.baseUnit))
Picker("entspricht", selection: $zweitBasis) {
Picker("Umrechnen in", selection: Binding(
get: { zweitBasis },
set: { waehleZweitart($0) }
)) {
Text(" keine ").tag("")
ForEach(zweitBasisOptionen, id: \.self) { basis in
Text(display.baseUnitLabel(basis)).tag(basis)
}
}
if !zweitBasis.isEmpty {
QuantityField(label: "das sind", text: $zweitMenge,
QuantityField(label: "Menge", text: $zweitAnzahl,
suffix: display.baseUnitLabel(current.baseUnit))
QuantityField(label: zweitVerb, text: $zweitMenge,
suffix: display.baseUnitLabel(zweitBasis))
}
} header: {
Text("Zweiteinheit (optional)")
Text("\(zweitTitel) (optional)")
} footer: {
Text(zweitHinweis)
}
@@ -385,6 +391,44 @@ struct ProductDetailView: View {
["piece", "gram", "milliliter"].filter { $0 != current.baseUnit }
}
/// Die Umrechnung heisst nach dem, was sie liefert Zweiteinheit" musste
/// man erst uebersetzen. Ohne gewaehlte Zielart ein neutraler Titel.
private var zweitTitel: String {
switch zweitBasis {
case "gram": return "Gewicht"
case "milliliter": return "Volumen"
case "piece": return "Stückzahl"
default: return "Umrechnung"
}
}
/// Verb passend zur Zielart und zur Anzahl: 1 Stück wiegt" / 3 wiegen".
private var zweitVerb: String {
let eins = Double(zweitAnzahl.replacingOccurrences(of: ",", with: ".")) == 1
switch zweitBasis {
case "gram": return eins ? "wiegt" : "wiegen"
case "milliliter": return eins ? "fasst" : "fassen"
case "piece": return eins ? "ist" : "sind"
default: return "sind"
}
}
/// Das Gebinde beim Namen nennen: 1 Packung sind ". Ohne Auswahl der
/// Standard, sonst stuende dort 1 sind 3 Stück".
private var gebindeName: String {
packageLabel.trimmingCharacters(in: .whitespaces).isEmpty ? "Packung" : packageLabel
}
/// Zielart waehlen und die linke Zahl EINMALIG mit der Packungsgroesse
/// vorbelegen im Normalfall ist sie genau das. Bewusst keine dauerhafte
/// Kopplung: wer die Packung spaeter aendert, hat dieselbe Ware.
private func waehleZweitart(_ basis: String) {
zweitBasis = basis
if !basis.isEmpty, zweitAnzahl.isEmpty {
zweitAnzahl = packageSize.isEmpty ? "1" : packageSize
}
}
/// Kontrolle unter der Eingabe: 1 Stück 83,33 g".
private var zweitHinweis: String {
let n = Double(zweitAnzahl.replacingOccurrences(of: ",", with: ".")) ?? 0
@@ -394,7 +438,8 @@ struct ProductDetailView: View {
+ "Gruppen der anderen Art und lässt sich darin ein- und auslagern."
}
return "1 \(display.baseUnitLabel(current.baseUnit))\(formatAmount(m / n)) "
+ "\(display.baseUnitLabel(zweitBasis)). Bestände bleiben beim Ändern "
+ "\(display.baseUnitLabel(zweitBasis)). Damit zählt der Artikel auch in "
+ "Gruppen, die in dieser Einheit rechnen. Bestände bleiben beim Ändern "
+ "unverändert nur ihre Umrechnung verschiebt sich."
}