Files
Vorrania/backend/app/services/items.py
T
ScarriffleandClaude Opus 5 467ef3c804 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>
2026-09-17 09:16:47 +02:00

50 lines
1.8 KiB
Python

"""Einzelstücke (Items): UID-Erzeugung und Anreicherung für die Ausgabe."""
from __future__ import annotations
import secrets
from sqlalchemy.orm import Session
from ..models import Item, ItemLoan
from ..schemas import ItemOut
from .loans import loan_to_out
# Ohne 0/O/1/I/L, damit die UID auf einem Etikett eindeutig lesbar bleibt.
_ALPHABET = "ABCDEFGHJKMNPQRSTUVWXYZ23456789"
def generate_uid(db: Session, length: int = 8) -> str:
"""Kurze, eindeutige UID erzeugen (bei Kollision neu würfeln)."""
while True:
code = "".join(secrets.choice(_ALPHABET) for _ in range(length))
if db.query(Item).filter(Item.uid == code).first() is None:
return code
def _offener(item: Item) -> ItemLoan | None:
"""Der laufende Verleih aus den schon geladenen Verleihen des Stücks.
Bewusst über die Beziehung statt per eigener Abfrage: So bleibt die
Einzelstück-Liste bei einer Abfrage, wenn der Aufrufer ``loans`` mitlädt.
"""
for loan in item.loans:
if loan.returned_on is None:
return loan
return None
def item_to_out(item: Item) -> ItemOut:
"""ItemOut inkl. Lagerort-/Shop-/Produktnamen und laufendem Verleih."""
out = ItemOut.model_validate(item)
out.location_name = item.location.name if item.location else None
out.shop_name = item.shop.name if item.shop else None
out.product_name = item.product.name if item.product else None
out.product_brand = item.product.brand if item.product else None
kategorie = item.product.category if item.product else None
out.category_id = kategorie.id if kategorie else None
out.category_name = kategorie.name if kategorie else None
loan = _offener(item)
out.loan = loan_to_out(loan) if loan is not None else None
return out