import SwiftUI /// Mengen-Erfassung nach dem Scan: mehrere Chargen mit eigenem MHD. struct CheckInFormView: View { let product: Product let units: [Unit] let locations: [StorageLocation] var onDone: (String) -> Void @Environment(\.dismiss) private var dismiss struct Line: Identifiable { let id = UUID() var quantity: String = "1" var hasDate: Bool = false var bestBefore: Date = Date() } @State private var lines: [Line] = [Line()] @State private var unit: String = "" @State private var locationId: Int? @State private var busy = false @State private var error: String? /// Einheiten der passenden Art plus das Gebinde des Artikels. private var unitOptions: [String] { var options = units.filter { $0.kind == product.kind }.map(\.name) if let size = product.packageSize, size > 0 { options.append(product.packageLabel ?? "Packung") } return options } private var packageOptionName: String? { guard let size = product.packageSize, size > 0 else { return nil } return product.packageLabel ?? "Packung" } var body: some View { Form { Section { HStack { VStack(alignment: .leading) { Text(product.name).font(.headline) Text("Bestand: \(format(product.stockInArticleUnits)) \(product.articleUnitLabel)") .font(.caption).foregroundStyle(.secondary) } Spacer() } } Section("Einheit") { Picker("Einheit", selection: $unit) { ForEach(unitOptions, id: \.self) { Text($0).tag($0) } } if !locations.isEmpty { Picker("Lagerort", selection: $locationId) { Text("– keiner –").tag(Int?.none) ForEach(locations) { location in Text(location.name).tag(Int?.some(location.id)) } } } } Section("Chargen") { ForEach($lines) { $line in VStack(alignment: .leading, spacing: 6) { HStack { // Ohne Beschriftung sah die vorbelegte 1 wie eine // Nummerierung der Charge aus. Text("Menge") Spacer(minLength: 12) TextField("Menge", text: $line.quantity) .keyboardType(.decimalPad) .multilineTextAlignment(.trailing) .frame(maxWidth: 110) if lines.count > 1 { Button(role: .destructive) { lines.removeAll { $0.id == line.id } } label: { Image(systemName: "trash") } .buttonStyle(.borderless) } } Toggle("MHD angeben", isOn: $line.hasDate) if line.hasDate { DatePicker("MHD", selection: $line.bestBefore, displayedComponents: .date) } } .padding(.vertical, 2) } Button { lines.append(Line()) } label: { Label("Weitere Charge (anderes MHD)", systemImage: "plus") } } if let error { Section { Text(error).foregroundStyle(.red).font(.callout) } } Section { Button(busy ? "Speichern…" : "Einlagern") { Task { await submit() } } .disabled(busy) } } .navigationTitle("Einlagern") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .topBarLeading) { Button("Abbrechen") { dismiss() } } } .onAppear { // Gibt es ein Gebinde, ist das die naheliegende Eingabe. unit = packageOptionName ?? product.unitName } } private func format(_ value: Double) -> String { value == value.rounded() ? String(Int(value)) : String(format: "%.2f", value) } private func submit() async { error = nil let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd" let payloadLines: [CheckInLine] = lines.compactMap { line in guard let quantity = Double(line.quantity.replacingOccurrences(of: ",", with: ".")), quantity > 0 else { return nil } return CheckInLine( quantity: quantity, bestBefore: line.hasDate ? formatter.string(from: line.bestBefore) : nil, locationId: locationId ) } guard !payloadLines.isEmpty else { error = "Bitte mindestens eine Menge angeben." return } busy = true defer { busy = false } do { let response = try await APIClient.shared.checkInBatch( BatchCheckInRequest(productId: product.id, unit: unit, lines: payloadLines) ) let total = response.productStock / product.articleUnitFactor onDone("Eingelagert. Neuer Bestand: \(format(total)) \(product.articleUnitLabel)") dismiss() } catch { self.error = error.localizedDescription } } }