Verwendungsstatus: Backend fuer "getragen / in Reinigung / in Reparatur"

Einzelstuecke hatten bisher keinen Zustand - der Lagerort sagt, wohin ein
Stueck gehoert, aber nicht, woran es gerade ist. Neue Tabelle item_usages
nach dem Vorbild von item_loans: ein Zeitraum je Zustand, ended_at NULL
heisst "laeuft noch", und ein Teilindex laesst nur einen laufenden zu.

"Verfuegbar" wird bewusst nicht gespeichert - keine offene Zeile IST
verfuegbar, wie bei lots.opened_on und beim Verleih. Damit kostet der
Ruhezustand nichts und die Tabelle fuellt sich nicht mit Rauschen.
Abgeschlossene Zeilen bleiben stehen, sie sind der Verlauf.

Eigener Endpunkt (PUT /items/{id}/verwendung) und ausdruecklich nicht
PATCH /items/{id}: Das ist Admins vorbehalten, und die App schickt dort
immer alle Felder mitsamt null - jedes Speichern wuerde den Status
stillschweigend zuruecksetzen. Umstellen darf deshalb jeder angemeldete
Benutzer, wie Verleihen.

Der Zustand aendert den Bestand nicht und schreibt keine Bewegung: ein
getragenes Hemd ist weiterhin im Haushalt, anders als ein verliehenes.

Sichtbar nur bei Kategorien mit usage_tracking. Ein Haken an einer
Oberkategorie wandert in den Unterbaum, weil die Garderobe als
Kategoriebaum ja schon existiert. Die Markierung fahrt in beiden
Sicherungswegen mit; der Verlauf nicht, weil Einzelstuecke dort
ueberhaupt nicht vorkommen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-10-20 21:47:02 +02:00
co-authored by Claude Opus 5
parent 8c752e3d1f
commit 98e2d18142
10 changed files with 691 additions and 6 deletions
+6 -1
View File
@@ -9,7 +9,7 @@ import re
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile, status
from fastapi.responses import Response
from sqlalchemy.orm import Session, joinedload
from sqlalchemy.orm import Session, joinedload, selectinload
from ..database import get_db
from ..deps import get_current_user, require_admin
@@ -17,6 +17,7 @@ from ..models import (
Item,
ItemDocument,
ItemLoan,
ItemUsage,
Location,
Movement,
MovementType,
@@ -166,6 +167,10 @@ def list_all_items(
# Verleihe samt Konto mitladen: Sonst fragt item_to_out je
# verliehenem Stueck einzeln nach (N+1).
joinedload(Item.loans).joinedload(ItemLoan.user),
# selectin und nicht joined: Hier haengen schon drei Sammlungen
# dran, eine vierte vervielfachte die Zeilen ueber Kreuz. So kostet
# es eine zusaetzliche Abfrage statt eines Kreuzprodukts.
selectinload(Item.usages).joinedload(ItemUsage.user),
)
.order_by(Item.id)
.all()