Einzelstuecke verleihen: eigener Vorgang statt Lagerort-Wert

Der Lagerort bleibt beim Verleihen stehen - er sagt weiter, wohin das
Stueck nach der Rueckgabe zurueckgehoert. Ein Lagerort-Wert "verliehen"
haette diese Information vernichtet. Stattdessen eine eigene Tabelle
item_loans mit Entleiher und Grund als Freitext, Verleihdatum und einem
optionalen Rueckgabedatum: "kurz mal mitgenommen" soll nicht an einem
Datumszwang scheitern.

Zurueckgegebene Vorgaenge bleiben stehen, damit die Tabelle gleichzeitig
die Verleih-Historie je Stueck ist. Ein Teilindex auf die offenen
Vorgaenge haelt fest, dass ein Stueck nicht zweimal gleichzeitig
verliehen sein kann - wie bei den "Ueberall"-Mindestbestaenden, weil
Postgres NULLs in UNIQUE als verschieden zaehlt.

Verliehen heisst nicht im Bestand: Ein Stueck, das nicht im Haus ist,
zaehlt nirgends mehr mit - weder am Lagerort noch im Gesamtbestand.
Entschieden wird das an einer Stelle (services/loans.py::nicht_verliehen),
die alle fuenf Zaehlungen benutzen. Sonst behauptete "Was liegt hier?"
weiterhin, die Kamera liege im Regal. Folge: Faellt ein verliehenes
Stueck unter den Mindestbestand, erscheint der Artikel auf der
Einkaufsliste.

Verleihen und Zuruecknehmen darf jeder angemeldete Benutzer - es ist eine
Buchung wie Ein- und Auslagern, keine Stammdatenaenderung - und wird mit
dem Konto protokolliert. Nur das Loeschen eines Vorgangs bleibt Admins
vorbehalten, weil es Historie vernichtet.

Bewusst keine Movement-Bewegung je Verleih: Der Verlauf rechnet den
Bestand rueckwaerts aus vorzeichenbehafteten Bewegungen, ein Abgang ohne
Gegenbuchung wuerde diese Kurve verfaelschen.

GET /loans?due_in= liefert die faelligen Verleihe und ist damit schon die
Datenquelle fuer die spaetere Rueckgabe-Erinnerung der App. Oberflaeche in
Web und App fehlt noch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-09-17 09:16:47 +02:00
co-authored by Claude Opus 5
parent ba84e4ea01
commit 467ef3c804
12 changed files with 833 additions and 12 deletions
+51
View File
@@ -770,6 +770,54 @@ class ItemDocumentUploadOut(ItemDocumentOut, DocumentSuggestions):
"""Antwort nach dem Upload – Beleg-Metadaten plus die geschätzten Werte."""
class LoanCreate(BaseModel):
"""Ein Einzelstück verleihen. Nur der Name ist Pflicht."""
borrower: str = Field(min_length=1, max_length=120)
reason: str | None = Field(default=None, max_length=255)
# Leer = heute; rückdatieren erlaubt (man trägt es oft später nach).
lent_on: date | None = None
# Erwartete Rückgabe – optional, siehe ItemLoan.
due_on: date | None = None
class LoanUpdate(BaseModel):
"""Laufenden oder abgeschlossenen Verleih korrigieren (Tippfehler, Datum)."""
borrower: str | None = Field(default=None, min_length=1, max_length=120)
reason: str | None = Field(default=None, max_length=255)
lent_on: date | None = None
due_on: date | None = None
returned_on: date | None = None
class LoanReturn(BaseModel):
"""Rückgabe buchen. Leeres Datum = heute."""
returned_on: date | None = None
class LoanOut(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
item_id: int
borrower: str
reason: str | None = None
lent_on: date
due_on: date | None = None
returned_on: date | None = None
created_at: datetime
# Wer herausgegeben hat (Konto kann später gelöscht sein).
username: str | None = None
# Tage über dem Rückgabedatum; None ohne Datum oder wenn noch nicht fällig.
# Vom Server gerechnet, damit App und Web nicht getrennt rechnen.
days_overdue: int | None = None
# Für die Übersicht „was ist gerade draußen" mitgeliefert:
item_uid: str | None = None
product_id: int | None = None
product_name: str | None = None
product_brand: str | None = None
location_id: str | None = None
location_name: str | None = None
class ItemOut(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
@@ -791,6 +839,9 @@ class ItemOut(BaseModel):
category_id: int | None = None
category_name: str | None = None
documents: list[ItemDocumentOut] = []
# Der offene Verleih, falls das Stück gerade draußen ist – sonst None.
# Der Lagerort bleibt daneben stehen: dorthin gehört es zurück.
loan: LoanOut | None = None
# ---- Views ----