iOS: Mindestbestand je Lagerort (Produkte) + Einkaufsliste je Ort
- Produkt-Detail: neuer Editor „Mindestbestand je Lagerort" (Zeilen: Lagerort + Menge in Artikeleinheiten), zusaetzlich zum Gesamt-Mindestbestand. - Einkaufsliste: Abschnitte „Bedarf: <Lagerort>" mit Produkten und Gruppen, die am Ort unter dem dort hinterlegten Mindestbestand liegen. - Models/Client: LocationMinStock(+In), LocationNeeds*, setProductLocationMinStock, shoppingByLocation; Product um location_min_stocks erweitert. (Gruppen-Bedarf je Ort ist vorerst nur im Web pflegbar, wird aber in der iOS- Einkaufsliste mit angezeigt.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -132,6 +132,18 @@ struct ProductDetailView: View {
|
||||
}
|
||||
}
|
||||
|
||||
Section {
|
||||
NavigationLink {
|
||||
ProductLocationMinView(product: current) { await reload() }
|
||||
} label: {
|
||||
let n = (current.locationMinStocks ?? []).count
|
||||
Label(n > 0 ? "Mindestbestand je Lagerort (\(n))" : "Mindestbestand je Lagerort",
|
||||
systemImage: "mappin.and.ellipse")
|
||||
}
|
||||
} footer: {
|
||||
Text("Bedarf je Lagerort (z.B. Ferienhaus, Zuhause), zusätzlich zum Gesamt-Mindestbestand.")
|
||||
}
|
||||
|
||||
Section("Erkennung") {
|
||||
LabeledContent("Barcode", value: current.barcode ?? "–")
|
||||
if !current.barcodes.isEmpty {
|
||||
@@ -399,3 +411,98 @@ struct LotEditView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Mindestbestand je Lagerort
|
||||
|
||||
/// Bedarf eines Produkts je Lagerort bearbeiten (zusätzlich zum globalen
|
||||
/// Mindestbestand). Menge in Artikeleinheiten.
|
||||
struct ProductLocationMinView: View {
|
||||
let product: Product
|
||||
var onChanged: (() async -> Void)? = nil
|
||||
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
private struct MinRow: Identifiable {
|
||||
let id = UUID()
|
||||
var locationId: Int?
|
||||
var amount: String
|
||||
}
|
||||
|
||||
@State private var locations: [StorageLocation] = []
|
||||
@State private var rows: [MinRow] = []
|
||||
@State private var busy = false
|
||||
@State private var error: String?
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section {
|
||||
Text("Bedarf je Lagerort (z.B. Ferienhaus, Zuhause). Menge in \(product.articleUnitLabel).")
|
||||
.font(.caption).foregroundStyle(.secondary)
|
||||
}
|
||||
ForEach($rows) { $row in
|
||||
HStack {
|
||||
Picker("Lagerort", selection: $row.locationId) {
|
||||
Text("– wählen –").tag(Int?.none)
|
||||
ForEach(locations) { l in Text(l.name).tag(Int?.some(l.id)) }
|
||||
}
|
||||
TextField("Menge", text: $row.amount)
|
||||
.keyboardType(.decimalPad)
|
||||
.multilineTextAlignment(.trailing)
|
||||
.frame(width: 70)
|
||||
Button(role: .destructive) {
|
||||
rows.removeAll { $0.id == row.id }
|
||||
} label: {
|
||||
Image(systemName: "trash")
|
||||
}
|
||||
.buttonStyle(.borderless)
|
||||
}
|
||||
}
|
||||
Button {
|
||||
rows.append(MinRow(locationId: nil, amount: ""))
|
||||
} label: {
|
||||
Label("Lagerort hinzufügen", systemImage: "plus")
|
||||
}
|
||||
if let error {
|
||||
Section { Text(error).foregroundStyle(.red).font(.callout) }
|
||||
}
|
||||
}
|
||||
.navigationTitle("Bedarf je Lagerort")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
Button(busy ? "Speichern…" : "Speichern") { Task { await save() } }
|
||||
.disabled(busy)
|
||||
}
|
||||
}
|
||||
.task { await load() }
|
||||
}
|
||||
|
||||
private func load() async {
|
||||
locations = (try? await APIClient.shared.locations()) ?? []
|
||||
rows = (product.locationMinStocks ?? []).map {
|
||||
MinRow(locationId: $0.locationId, amount: formatAmount($0.minStock))
|
||||
}
|
||||
}
|
||||
|
||||
private func save() async {
|
||||
busy = true
|
||||
defer { busy = false }
|
||||
var list: [LocationMinStockIn] = []
|
||||
var gesehen: Set<Int> = []
|
||||
for row in rows {
|
||||
guard let loc = row.locationId, !gesehen.contains(loc) else { continue }
|
||||
let wert = Double(row.amount.replacingOccurrences(of: ",", with: ".")) ?? 0
|
||||
if wert > 0 {
|
||||
gesehen.insert(loc)
|
||||
list.append(LocationMinStockIn(locationId: loc, minStock: wert))
|
||||
}
|
||||
}
|
||||
do {
|
||||
_ = try await APIClient.shared.setProductLocationMinStock(id: product.id, list)
|
||||
await onChanged?()
|
||||
dismiss()
|
||||
} catch {
|
||||
self.error = error.localizedDescription
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user