App: Einzelstuecke verleihen - alle Scan-Wege enden schon dort

Verleih-Abschnitt im Einzelstueck: an wen, seit wann, bis wann, dazu
Herausgeben bzw. Zuruecknehmen und der Verleih-Verlauf. Pflicht ist nur der
Name; Grund und Rueckgabedatum sind abschaltbar, weil "kurz mal
mitgenommen" nicht an einem Datum scheitern soll.

Der Abschnitt sitzt bewusst oben in ItemEditView, und zwar nicht nur der
Wichtigkeit wegen: QR ueber Nachschlagen, eingenaehtes NFC-Tag und ein
angetipptes Tag (vorrania://i/<UID>) landen alle in genau dieser Ansicht.
Damit ist "scannen und verleihen" ueberall moeglich, ohne einen eigenen
Weg dafuer zu bauen.

In der Einzelstueck-Liste steht der Verleih unter dem Stueck - ueberfaellig
in Rot - und das Filter-Menue kann auf Verliehen, Ueberfaellig oder Im Haus
eingrenzen. Wo etwas ist, war bisher immer der Lagerort; ist es verliehen,
ist der Entleiher die Antwort, und der Lagerort steht daneben.

days_overdue rechnet der Server (GET /loans, ItemOut.loan), damit App und
Web nicht getrennt rechnen und nicht auseinanderlaufen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-09-17 09:36:05 +02:00
co-authored by Claude Opus 5
parent 1332dd86fa
commit aa72d8342a
7 changed files with 366 additions and 9 deletions
+68 -1
View File
@@ -1046,6 +1046,68 @@ struct ItemDocument: Codable, Identifiable, Hashable {
}
}
/// Ein Verleih-Vorgang eines Einzelstuecks. `returnedOn == nil` heisst „noch
/// draussen"; je Stueck gibt es hoechstens einen solchen offenen Verleih.
///
/// `daysOverdue` rechnet der Server, damit App und Web nicht getrennt rechnen -
/// nil heisst: ohne Frist, zurueck, oder noch nicht faellig.
struct Loan: Codable, Identifiable, Hashable {
let id: Int
let itemId: Int
let borrower: String
let reason: String?
let lentOn: String // "yyyy-MM-dd"
let dueOn: String?
let returnedOn: String?
let createdAt: String
let username: String?
let daysOverdue: Int?
let itemUid: String?
let productId: Int?
let productName: String?
let productBrand: String?
let locationId: String?
let locationName: String?
var istOffen: Bool { returnedOn == nil }
var istUeberfaellig: Bool { (daysOverdue ?? 0) > 0 }
enum CodingKeys: String, CodingKey {
case id, borrower, reason, username
case itemId = "item_id"
case lentOn = "lent_on"
case dueOn = "due_on"
case returnedOn = "returned_on"
case createdAt = "created_at"
case daysOverdue = "days_overdue"
case itemUid = "item_uid"
case productId = "product_id"
case productName = "product_name"
case productBrand = "product_brand"
case locationId = "location_id"
case locationName = "location_name"
}
}
struct LoanCreateRequest: Encodable {
let borrower: String
var reason: String? = nil
var lentOn: String? = nil
var dueOn: String? = nil
enum CodingKeys: String, CodingKey {
case borrower, reason
case lentOn = "lent_on"
case dueOn = "due_on"
}
}
struct LoanReturnRequest: Encodable {
var returnedOn: String? = nil
enum CodingKeys: String, CodingKey { case returnedOn = "returned_on" }
}
struct Item: Codable, Identifiable, Hashable {
let id: Int
let uid: String
@@ -1065,9 +1127,14 @@ struct Item: Codable, Identifiable, Hashable {
let categoryId: Int?
let categoryName: String?
let documents: [ItemDocument]
/// Der offene Verleih, falls das Stueck gerade draussen ist - sonst nil.
/// Der Lagerort steht daneben unveraendert: dorthin gehoert es zurueck.
let loan: Loan?
var istVerliehen: Bool { loan != nil }
enum CodingKeys: String, CodingKey {
case id, uid, note, currency, documents
case id, uid, note, currency, documents, loan
case productId = "product_id"
case locationId = "location_id"
case locationName = "location_name"