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
+28
View File
@@ -276,6 +276,34 @@ actor APIClient {
return try await send(request, as: Item.self)
}
// MARK: - Verleih von Einzelstuecken
/// Herausgeben. Scheitert mit 409, wenn das Stueck schon verliehen ist.
func lendItem(id: Int, _ payload: LoanCreateRequest) async throws -> Loan {
var request = try makeRequest("/items/\(id)/loan", method: "POST")
try jsonBody(&request, payload)
return try await send(request, as: Loan.self)
}
/// Rueckgabe buchen. Leeres Datum = heute.
func returnItem(id: Int, _ payload: LoanReturnRequest = LoanReturnRequest()) async throws -> Loan {
var request = try makeRequest("/items/\(id)/return", method: "POST")
try jsonBody(&request, payload)
return try await send(request, as: Loan.self)
}
/// Verleih-Historie eines Stuecks, neueste zuerst.
func itemLoans(id: Int) async throws -> [Loan] {
try await send(try makeRequest("/items/\(id)/loans"), as: [Loan].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] {
let pfad = dueIn.map { "/loans?due_in=\($0)" } ?? "/loans"
return try await send(try makeRequest(pfad), as: [Loan].self)
}
func removeItem(id: Int, _ payload: ItemRemoveRequest) async throws {
var request = try makeRequest("/items/\(id)/remove", method: "POST")
try jsonBody(&request, payload)