Verwendungsstatus: in der App umstellen, filtern und nachsehen
Im Einzelstueck steht jetzt ueber dem Verleih ein Abschnitt "Verwendung" mit vier Symbolen: verfuegbar, in Verwendung, in Reinigung, in Reparatur. Ein Tipp bucht sofort, ohne "Speichern" - wie beim Verleih, denn alle Scan-Wege (QR, NFC, vorrania://i/<UID>) landen genau in dieser Ansicht. Vier Woerter waeren auf einem iPhone zu breit, vier Symbole passen immer; darunter steht der Name im Klartext samt "seit wann". Dazu der Verwendungs-Verlauf mit Zeitstempeln und Konto, und in der Einzelstueck-Liste ein Filter ueber alle vier Zustaende plus die Zeile unter dem Namen, solange etwas laeuft. Stuecke ohne Verwendungsstatus fallen aus dem Filter "Verfuegbar" heraus - ein Schraubenzieher ist nicht verfuegbar, er fuehrt den Zustand gar nicht. Der Haken je Kategorie sitzt im Kategorie-Editor und gilt beim Bearbeiten auch fuer die Unterkategorien. Zwei Dinge, die beim Pruefen am Simulator auffielen: Die Leiste haette beim Oeffnen jedes Mal einmal ins Leere gebucht, weil onChange auch beim Laden feuert - dagegen der Abgleich mit dem zuletzt gemeldeten Stand. Und SQLite liefert Zeitstempel ohne Zeitzone, woran ISO8601DateFormatter scheitert; in der App stand deshalb der rohe ISO-Text. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
b9d097fe96
commit
290ff9341a
@@ -339,6 +339,25 @@ actor APIClient {
|
||||
try await send(try makeRequest("/items/\(id)/loans"), as: [Loan].self)
|
||||
}
|
||||
|
||||
// MARK: - Verwendungsstatus
|
||||
|
||||
/// Umstellen; `status` nil heisst verfuegbar. Eigener Endpunkt aus
|
||||
/// demselben Grund wie Zubehoer, und weil updateItem Admins vorbehalten ist
|
||||
/// - umstellen darf jeder angemeldete Benutzer.
|
||||
///
|
||||
/// Antwortet mit dem ganzen Stueck, die Aufrufstelle braucht also kein
|
||||
/// zweites Laden.
|
||||
func setzeVerwendung(id: Int, status: VerwendungStatus?) async throws -> Item {
|
||||
var request = try makeRequest("/items/\(id)/verwendung", method: "PUT")
|
||||
try jsonBody(&request, VerwendungSetzenRequest(status: status?.rawValue))
|
||||
return try await send(request, as: Item.self)
|
||||
}
|
||||
|
||||
/// Verwendungs-Verlauf eines Stuecks, neueste zuerst.
|
||||
func itemVerwendungen(id: Int) async throws -> [Verwendung] {
|
||||
try await send(try makeRequest("/items/\(id)/verwendungen"), as: [Verwendung].self)
|
||||
}
|
||||
|
||||
/// Offene Verleihe, faelligste zuerst. `dueIn` grenzt auf die ein, deren
|
||||
/// Rueckgabe in so vielen Tagen ansteht (ueberfaellige sind immer dabei).
|
||||
func loans(dueIn: Int? = nil) async throws -> [Loan] {
|
||||
|
||||
@@ -11,6 +11,28 @@ struct ItemListView: View {
|
||||
@State private var locationFilter: String? // Lagerort-ID
|
||||
@State private var categoryFilter: Int? // Kategorie-ID (Unterkategorien zählen mit)
|
||||
@State private var verleihFilter: VerleihFilter = .alle
|
||||
@State private var verwendungFilter: VerwendungFilter = .alle
|
||||
|
||||
/// Blickwinkel auf den Verwendungszustand – „was liegt gerade in der
|
||||
/// Wäsche?" ist die Frage dahinter. „Verfügbar" ist dabei das Fehlen eines
|
||||
/// laufenden Zeitraums, kein eigener Serverwert.
|
||||
enum VerwendungFilter: Hashable, CaseIterable {
|
||||
case alle
|
||||
case verfuegbar
|
||||
case status(VerwendungStatus)
|
||||
|
||||
static var allCases: [VerwendungFilter] {
|
||||
[.alle, .verfuegbar] + VerwendungStatus.allCases.map { .status($0) }
|
||||
}
|
||||
|
||||
var label: String {
|
||||
switch self {
|
||||
case .alle: return "Alle"
|
||||
case .verfuegbar: return "Verfügbar"
|
||||
case let .status(s): return s.label
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Blickwinkel auf den Verleihzustand – „was ist gerade draußen?" ist die
|
||||
/// Frage, die man im Alltag an diese Liste stellt.
|
||||
@@ -97,6 +119,16 @@ struct ItemListView: View {
|
||||
case .ueberfaellig: if !(it.loan?.istUeberfaellig ?? false) { return false }
|
||||
case .imHaus: if it.loan != nil { return false }
|
||||
}
|
||||
switch verwendungFilter {
|
||||
case .alle: break
|
||||
// Stuecke ohne Verwendungsstatus fallen hier bewusst raus: Ein
|
||||
// Schraubenzieher ist nicht „verfuegbar", er fuehrt den Zustand gar
|
||||
// nicht.
|
||||
case .verfuegbar:
|
||||
if !it.zeigtVerwendung || it.verwendungStatus != nil { return false }
|
||||
case let .status(gesucht):
|
||||
if it.verwendungStatus != gesucht { return false }
|
||||
}
|
||||
if !q.isEmpty {
|
||||
let heu = [it.productName, it.uid, it.note, it.productBrand]
|
||||
.compactMap { $0 }.joined(separator: " ").lowercased()
|
||||
@@ -108,6 +140,7 @@ struct ItemListView: View {
|
||||
|
||||
private var hatFilter: Bool {
|
||||
locationFilter != nil || categoryFilter != nil || verleihFilter != .alle
|
||||
|| verwendungFilter != .alle
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
@@ -193,6 +226,11 @@ struct ItemListView: View {
|
||||
check(f.label, verleihFilter == f) { verleihFilter = f }
|
||||
}
|
||||
}
|
||||
Menu("Verwendung") {
|
||||
ForEach(VerwendungFilter.allCases, id: \.self) { f in
|
||||
check(f.label, verwendungFilter == f) { verwendungFilter = f }
|
||||
}
|
||||
}
|
||||
if !filterKategorien.isEmpty {
|
||||
Menu("Kategorie") {
|
||||
check("Alle", categoryFilter == nil) { categoryFilter = nil }
|
||||
@@ -204,7 +242,8 @@ struct ItemListView: View {
|
||||
if hatFilter {
|
||||
Divider()
|
||||
Button(role: .destructive) {
|
||||
locationFilter = nil; categoryFilter = nil; verleihFilter = .alle
|
||||
locationFilter = nil; categoryFilter = nil
|
||||
verleihFilter = .alle; verwendungFilter = .alle
|
||||
} label: {
|
||||
Label("Filter zurücksetzen", systemImage: "xmark.circle")
|
||||
}
|
||||
@@ -263,6 +302,13 @@ struct ItemRow: View {
|
||||
.foregroundStyle(loan.istUeberfaellig ? Color.red : .secondary)
|
||||
.lineLimit(2)
|
||||
}
|
||||
// Nur, wenn etwas laeuft: „Verfuegbar" unter jeder zweiten
|
||||
// Zeile waere Rauschen.
|
||||
if let status = item.verwendungStatus {
|
||||
Label(status.label, systemImage: status.symbol)
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
|
||||
@@ -20,6 +20,41 @@ private func anzeigeDatum(_ iso: String?) -> String {
|
||||
return f.string(from: d)
|
||||
}
|
||||
|
||||
/// ISO-Zeitstempel -> "20.09.2026, 18:42". Anders als bei Daten steht hier ein
|
||||
/// voller Zeitpunkt: Ein Hemd wechselt mehrmals am Tag den Zustand.
|
||||
func anzeigeZeitpunkt(_ iso: String?) -> String {
|
||||
guard let iso else { return "–" }
|
||||
guard let d = zeitpunktAus(iso) else { return iso }
|
||||
let f = DateFormatter()
|
||||
f.locale = Locale(identifier: "de_DE")
|
||||
f.dateFormat = "dd.MM.yyyy, HH:mm"
|
||||
return f.string(from: d)
|
||||
}
|
||||
|
||||
/// Zeitstempel des Servers lesen - mit und ohne Zeitzonenangabe.
|
||||
///
|
||||
/// Postgres liefert sie mit Zone ("…+00:00"), SQLite laesst sie weg. Ohne
|
||||
/// Angabe ist es UTC, denn der Server schreibt `datetime.now(UTC)`. Ohne diesen
|
||||
/// Zweig stuende in der App der rohe ISO-Text.
|
||||
private func zeitpunktAus(_ iso: String) -> Date? {
|
||||
let mitZone = ISO8601DateFormatter()
|
||||
mitZone.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
||||
if let d = mitZone.date(from: iso) { return d }
|
||||
mitZone.formatOptions = [.withInternetDateTime]
|
||||
if let d = mitZone.date(from: iso) { return d }
|
||||
|
||||
let ohneZone = DateFormatter()
|
||||
ohneZone.locale = Locale(identifier: "en_US_POSIX")
|
||||
ohneZone.timeZone = TimeZone(identifier: "UTC")
|
||||
for muster in ["yyyy-MM-dd'T'HH:mm:ss.SSSSSS",
|
||||
"yyyy-MM-dd'T'HH:mm:ss.SSS",
|
||||
"yyyy-MM-dd'T'HH:mm:ss"] {
|
||||
ohneZone.dateFormat = muster
|
||||
if let d = ohneZone.date(from: iso) { return d }
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/// Verleihzustand in einer Zeile: „Markus · zurueck bis 20.09.2026".
|
||||
/// Ueberfaelliges kommt beim Aufrufer rot daher.
|
||||
func verleihZeile(_ loan: Loan) -> String {
|
||||
@@ -242,6 +277,17 @@ struct ItemEditView: View {
|
||||
@State private var hasDue = false
|
||||
@State private var due = Date()
|
||||
@State private var busyLoan = false
|
||||
// Verwendung: der laufende Zustand (nil = verfuegbar), nachgeladen wie der
|
||||
// Verleih. `zeigtVerwendung` kommt vom uebergebenen Stueck und aendert sich
|
||||
// waehrend der Ansicht nicht.
|
||||
@State private var verwendung: VerwendungStatus?
|
||||
// Was der Server zuletzt gemeldet hat. Ohne diesen Abgleich buchte das
|
||||
// onChange der Leiste auch dann, wenn `verwendung` nur beim Laden gesetzt
|
||||
// wurde - also bei jedem Oeffnen einmal ins Leere.
|
||||
@State private var verwendungServer: VerwendungStatus?
|
||||
@State private var verwendungSeit: String?
|
||||
@State private var verwendungAnzahl = 0
|
||||
@State private var busyVerwendung = false
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
@@ -268,6 +314,39 @@ struct ItemEditView: View {
|
||||
}
|
||||
}
|
||||
|
||||
// Woran ist es gerade? Steht oben, weil alle Scan-Wege hier landen
|
||||
// und „gerade aus der Waesche geholt" ein Griff sein soll - also
|
||||
// eine Buchung wie der Verleih, kein Feld, das auf „Speichern"
|
||||
// wartet. Vier Woerter waeren auf einem iPhone zu breit, vier
|
||||
// Symbole passen immer.
|
||||
if item.zeigtVerwendung {
|
||||
Section {
|
||||
Picker("Verwendung", selection: $verwendung) {
|
||||
ForEach(VerwendungStatus.auswahl, id: \.self) { status in
|
||||
Image(systemName: VerwendungStatus.symbol(status))
|
||||
.tag(status)
|
||||
}
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
.disabled(busyVerwendung)
|
||||
.onChange(of: verwendung) { neu in
|
||||
Task { await setzeVerwendung(neu) }
|
||||
}
|
||||
if verwendungAnzahl > 0 {
|
||||
NavigationLink("Verwendungs-Verlauf (\(verwendungAnzahl))") {
|
||||
VerwendungHistorieView(uid: item.uid, itemId: item.id)
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
Text("Verwendung")
|
||||
} footer: {
|
||||
Text(busyVerwendung
|
||||
? "Moment…"
|
||||
: VerwendungStatus.label(verwendung)
|
||||
+ (verwendungSeit.map { " · seit \($0)" } ?? ""))
|
||||
}
|
||||
}
|
||||
|
||||
// Verleih steht vor allem anderen: Ist das Stueck draussen, ist das
|
||||
// die Antwort auf „wo ist es?". Alle Scan-Wege der App (QR, NFC,
|
||||
// vorrania://i/<UID>) landen in dieser Ansicht - deshalb genuegt der
|
||||
@@ -605,6 +684,42 @@ struct ItemEditView: View {
|
||||
} catch { self.error = error.localizedDescription }
|
||||
}
|
||||
|
||||
/// Verwendungszustand umstellen. Leer = verfuegbar.
|
||||
///
|
||||
/// Die Antwort traegt das ganze Stueck, ein zweites Laden entfaellt. Nur
|
||||
/// der Verlaufszaehler wird nachgeholt.
|
||||
private func setzeVerwendung(_ neu: VerwendungStatus?) async {
|
||||
guard neu != verwendungServer, !busyVerwendung else { return }
|
||||
busyVerwendung = true; defer { busyVerwendung = false }
|
||||
error = nil
|
||||
do {
|
||||
let frisch = try await APIClient.shared.setzeVerwendung(id: item.id, status: neu)
|
||||
uebernimm(frisch.verwendung)
|
||||
await ladeVerwendungen()
|
||||
onChanged?()
|
||||
} catch {
|
||||
self.error = error.localizedDescription
|
||||
// Zurueck auf den Stand des Servers, sonst zeigt die Leiste eine
|
||||
// Stellung an, die nie gebucht wurde.
|
||||
await ladeVerwendungen()
|
||||
}
|
||||
}
|
||||
|
||||
private func uebernimm(_ offen: Verwendung?) {
|
||||
verwendungServer = offen?.status
|
||||
verwendung = offen?.status
|
||||
verwendungSeit = offen.map { anzeigeZeitpunkt($0.startedAt) }
|
||||
}
|
||||
|
||||
/// Verlauf holen - fuer den Zaehler am Verlaufs-Eintrag und um den
|
||||
/// laufenden Zustand gegen den Server abzugleichen.
|
||||
private func ladeVerwendungen() async {
|
||||
guard item.zeigtVerwendung else { return }
|
||||
let verlauf = (try? await APIClient.shared.itemVerwendungen(id: item.id)) ?? []
|
||||
verwendungAnzahl = verlauf.count
|
||||
uebernimm(verlauf.first(where: { $0.laeuft }))
|
||||
}
|
||||
|
||||
/// Offenen Verleih und Historie frisch holen - nach jeder Buchung, damit die
|
||||
/// Ansicht nicht auf dem Stand des uebergebenen `item` haengen bleibt.
|
||||
private func ladeVerleihe() async {
|
||||
@@ -627,7 +742,9 @@ struct ItemEditView: View {
|
||||
loan = item.loan
|
||||
teile = item.teile
|
||||
gehoertZu = item.gehoertZu
|
||||
uebernimm(item.verwendung)
|
||||
await ladeVerleihe()
|
||||
await ladeVerwendungen()
|
||||
}
|
||||
|
||||
private func haengeAn(_ gewaehlt: Item) async {
|
||||
@@ -1075,6 +1192,54 @@ struct ItemAddSheet: View {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Verwendungs-Verlauf
|
||||
|
||||
/// Alle Verwendungszeitraeume eines Stuecks, neueste zuerst. Abgeschlossene
|
||||
/// bleiben erhalten - so ist nachvollziehbar, seit wann etwas in der Waesche
|
||||
/// liegt und wie oft es getragen wurde.
|
||||
struct VerwendungHistorieView: View {
|
||||
let uid: String
|
||||
let itemId: Int
|
||||
|
||||
@State private var zeilen: [Verwendung] = []
|
||||
@State private var geladen = false
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
if !geladen {
|
||||
Text("Wird geladen…").foregroundStyle(.secondary)
|
||||
} else if zeilen.isEmpty {
|
||||
Text("War noch nie in Verwendung.").foregroundStyle(.secondary)
|
||||
}
|
||||
ForEach(zeilen) { zeile in
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
HStack {
|
||||
Label(zeile.status.label, systemImage: zeile.status.symbol)
|
||||
.font(.body.bold())
|
||||
Spacer()
|
||||
if zeile.laeuft {
|
||||
Text("läuft").font(.caption).foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
Text(anzeigeZeitpunkt(zeile.startedAt)
|
||||
+ (zeile.endedAt.map { " – " + anzeigeZeitpunkt($0) } ?? ""))
|
||||
.font(.caption).foregroundStyle(.secondary)
|
||||
if let von = zeile.username {
|
||||
Text("von \(von)").font(.caption).foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 2)
|
||||
}
|
||||
}
|
||||
.navigationTitle("Verwendung \(uid)")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.task {
|
||||
zeilen = (try? await APIClient.shared.itemVerwendungen(id: itemId)) ?? []
|
||||
geladen = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Verleih-Verlauf
|
||||
|
||||
/// Alle Verleihe eines Stuecks, neueste zuerst. Zurueckgegebene Vorgaenge bleiben
|
||||
|
||||
@@ -757,6 +757,7 @@ struct CategoryEditor: View {
|
||||
|
||||
@State private var name: String
|
||||
@State private var tracking: String
|
||||
@State private var usageTracking: Bool
|
||||
@State private var parentId: Int? // nur beim Anlegen
|
||||
@State private var error: String?
|
||||
@State private var busy = false
|
||||
@@ -767,6 +768,7 @@ struct CategoryEditor: View {
|
||||
self.done = done
|
||||
_name = State(initialValue: item?.name ?? "")
|
||||
_tracking = State(initialValue: item?.tracking ?? "object")
|
||||
_usageTracking = State(initialValue: item?.fuehrtVerwendung ?? false)
|
||||
_parentId = State(initialValue: nil)
|
||||
}
|
||||
|
||||
@@ -812,6 +814,14 @@ struct CategoryEditor: View {
|
||||
}
|
||||
}
|
||||
|
||||
if !erbtVonEltern {
|
||||
Section {
|
||||
Toggle("Verwendungsstatus führen", isOn: $usageTracking)
|
||||
} footer: {
|
||||
Text("Einzelstücke dieser Kategorie führen, woran sie gerade sind – in Verwendung, in Reinigung, in Reparatur. Gedacht für Kleidung und Werkzeug, nicht für Vorräte. Beim Bearbeiten gilt die Einstellung auch für alle Unterkategorien.")
|
||||
}
|
||||
}
|
||||
|
||||
if let item {
|
||||
Section {
|
||||
NavigationLink {
|
||||
@@ -847,13 +857,17 @@ struct CategoryEditor: View {
|
||||
do {
|
||||
if let item {
|
||||
_ = try await APIClient.shared.updateCategory(
|
||||
id: item.id, CategoryUpdateRequest(name: n, tracking: tracking))
|
||||
id: item.id,
|
||||
CategoryUpdateRequest(name: n, tracking: tracking,
|
||||
usageTracking: usageTracking))
|
||||
} else {
|
||||
// Mit Oberkategorie die Art erben (tracking = nil), sonst die
|
||||
// gewählte Art.
|
||||
_ = try await APIClient.shared.createCategory(
|
||||
NewCategoryRequest(name: n, parentId: parentId,
|
||||
tracking: parentId == nil ? tracking : nil))
|
||||
tracking: parentId == nil ? tracking : nil,
|
||||
// Mit Oberkategorie die Markierung erben.
|
||||
usageTracking: parentId == nil ? usageTracking : nil))
|
||||
}
|
||||
done()
|
||||
dismiss()
|
||||
|
||||
@@ -1079,13 +1079,18 @@ struct CategoryItem: Codable, Identifiable, Hashable {
|
||||
let name: String
|
||||
let parentId: Int?
|
||||
let tracking: String?
|
||||
/// Ob Einzelstuecke dieser Kategorie einen Verwendungsstatus fuehren.
|
||||
/// Optional, weil ein aelterer Server das Feld nicht schickt.
|
||||
var usageTracking: Bool?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id, name, tracking
|
||||
case parentId = "parent_id"
|
||||
case usageTracking = "usage_tracking"
|
||||
}
|
||||
|
||||
var isObject: Bool { tracking == "object" }
|
||||
var fuehrtVerwendung: Bool { usageTracking ?? false }
|
||||
}
|
||||
|
||||
// MARK: - Hierarchische Stammdaten
|
||||
@@ -1266,6 +1271,73 @@ struct ItemDocument: Codable, Identifiable, Hashable {
|
||||
///
|
||||
/// `daysOverdue` rechnet der Server, damit App und Web nicht getrennt rechnen -
|
||||
/// nil heisst: ohne Frist, zurueck, oder noch nicht faellig.
|
||||
/// Woran ein Einzelstueck gerade ist.
|
||||
///
|
||||
/// "Verfuegbar" ist bewusst kein Serverwert, sondern das Fehlen eines
|
||||
/// laufenden Zeitraums - deshalb traegt der Ruhezustand hier `nil`. Absichtlich
|
||||
/// allgemein und nicht auf Kleidung zugeschnitten: Eine Bohrmaschine auf der
|
||||
/// Baustelle ist genauso "in Verwendung".
|
||||
enum VerwendungStatus: String, Codable, CaseIterable, Identifiable, Hashable {
|
||||
case inUse = "in_use"
|
||||
case cleaning = "cleaning"
|
||||
case repair = "repair"
|
||||
|
||||
var id: String { rawValue }
|
||||
|
||||
var label: String {
|
||||
switch self {
|
||||
case .inUse: return "In Verwendung"
|
||||
case .cleaning: return "In Reinigung"
|
||||
case .repair: return "In Reparatur"
|
||||
}
|
||||
}
|
||||
|
||||
var symbol: String {
|
||||
switch self {
|
||||
case .inUse: return "figure.walk"
|
||||
case .cleaning: return "drop"
|
||||
case .repair: return "wrench.and.screwdriver"
|
||||
}
|
||||
}
|
||||
|
||||
/// Die vier Schaltstellungen inklusive des Ruhezustands (nil).
|
||||
static let auswahl: [VerwendungStatus?] = [nil] + allCases.map { Optional($0) }
|
||||
|
||||
static func label(_ status: VerwendungStatus?) -> String {
|
||||
status?.label ?? "Verfügbar"
|
||||
}
|
||||
|
||||
static func symbol(_ status: VerwendungStatus?) -> String {
|
||||
status?.symbol ?? "checkmark.circle"
|
||||
}
|
||||
}
|
||||
|
||||
/// Ein Zeitraum in einem Verwendungszustand - laufend oder abgeschlossen.
|
||||
struct Verwendung: Codable, Identifiable, Hashable {
|
||||
let id: Int
|
||||
let itemId: Int
|
||||
let status: VerwendungStatus
|
||||
let startedAt: String
|
||||
/// nil = laeuft noch.
|
||||
let endedAt: String?
|
||||
let username: String?
|
||||
|
||||
var laeuft: Bool { endedAt == nil }
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id, status, username
|
||||
case itemId = "item_id"
|
||||
case startedAt = "started_at"
|
||||
case endedAt = "ended_at"
|
||||
}
|
||||
}
|
||||
|
||||
struct VerwendungSetzenRequest: Encodable {
|
||||
/// nil = verfuegbar. Muss AUCH dann mitgeschickt werden, sonst liest der
|
||||
/// Server den Vorgabewert und nicht "verfuegbar".
|
||||
let status: String?
|
||||
}
|
||||
|
||||
struct Loan: Codable, Identifiable, Hashable {
|
||||
let id: Int
|
||||
let itemId: Int
|
||||
@@ -1391,15 +1463,28 @@ struct Item: Codable, Identifiable, Hashable {
|
||||
/// Swift nimmt den Vorgabewert beim Dekodieren nicht her, es wuerde werfen.
|
||||
var zubehoer: [ItemKurz]?
|
||||
var gehoertZu: ItemKurz?
|
||||
/// Der laufende Verwendungszustand - nil heisst verfuegbar. Anders als beim
|
||||
/// Verleih bleibt das Stueck dabei im Bestand: getragen ist nicht weg.
|
||||
///
|
||||
/// Beide Felder optional, aus demselben Grund wie `zubehoer` oben: ein
|
||||
/// aelterer Server schickt sie gar nicht.
|
||||
var verwendung: Verwendung?
|
||||
/// Ob die Kategorie dieses Stuecks ueberhaupt einen Verwendungsstatus fuehrt.
|
||||
var verwendungMoeglich: Bool?
|
||||
|
||||
/// Zubehoer ohne Optional-Gehampel an der Aufrufstelle.
|
||||
var teile: [ItemKurz] { zubehoer ?? [] }
|
||||
|
||||
var istVerliehen: Bool { loan != nil }
|
||||
|
||||
/// Ohne Optional-Gehampel an der Aufrufstelle.
|
||||
var zeigtVerwendung: Bool { verwendungMoeglich ?? false }
|
||||
var verwendungStatus: VerwendungStatus? { verwendung?.status }
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id, uid, note, currency, documents, loan, zubehoer
|
||||
case id, uid, note, currency, documents, loan, zubehoer, verwendung
|
||||
case gehoertZu = "gehoert_zu"
|
||||
case verwendungMoeglich = "verwendung_moeglich"
|
||||
case productId = "product_id"
|
||||
case locationId = "location_id"
|
||||
case locationName = "location_name"
|
||||
@@ -1780,16 +1865,26 @@ struct NewCategoryRequest: Codable {
|
||||
let name: String
|
||||
let parentId: Int?
|
||||
var tracking: String? = nil
|
||||
/// nil = erbt vom Elternteil bzw. neue Oberkategorie = aus.
|
||||
var usageTracking: Bool? = nil
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case name, tracking
|
||||
case parentId = "parent_id"
|
||||
case usageTracking = "usage_tracking"
|
||||
}
|
||||
}
|
||||
|
||||
struct CategoryUpdateRequest: Codable {
|
||||
let name: String?
|
||||
let tracking: String?
|
||||
/// Wird vom Server in den ganzen Unterbaum uebernommen.
|
||||
var usageTracking: Bool? = nil
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case name, tracking
|
||||
case usageTracking = "usage_tracking"
|
||||
}
|
||||
}
|
||||
|
||||
struct NewShopRequest: Codable {
|
||||
|
||||
Reference in New Issue
Block a user