"""Verleih von Einzelstücken: Bestandsfilter, offener Verleih, Ausgabe. Ein verliehenes Stück ist nicht im Haus. Es zählt deshalb **nirgends** als Bestand – weder am Lagerort noch im Gesamtbestand; ``nicht_verliehen`` ist die eine Stelle, an der das steht, und wird von allen Zählungen benutzt (``services/stock.py``, ``crud.products_to_out_bulk``, Dashboard-Auswertungen). Der Lagerort am Stück bleibt dabei erhalten: Er sagt weiter, wohin es nach der Rückgabe zurückgehört. """ from __future__ import annotations from datetime import date from sqlalchemy.orm import Query, Session from ..models import Item, ItemLoan from ..schemas import LoanOut def nicht_verliehen(query: Query) -> Query: """Verliehene Einzelstücke aus einer Item-Abfrage nehmen. Arbeitet als korreliertes NOT EXISTS und ist damit unabhängig davon, ob die Abfrage ganze Items oder nur Spalten (``count``) auswählt. """ return query.filter(~Item.loans.any(ItemLoan.returned_on.is_(None))) def offener_verleih(db: Session, item_id: int) -> ItemLoan | None: """Der laufende Verleih eines Stücks, falls es gerade draußen ist.""" return ( db.query(ItemLoan) .filter(ItemLoan.item_id == item_id, ItemLoan.returned_on.is_(None)) .first() ) def tage_ueberfaellig(loan: ItemLoan, heute: date | None = None) -> int | None: """Tage über dem Rückgabedatum – None ohne Datum, zurück oder noch nicht fällig.""" if loan.due_on is None or loan.returned_on is not None: return None tage = ((heute or date.today()) - loan.due_on).days return tage if tage > 0 else None def loan_to_out(loan: ItemLoan, heute: date | None = None) -> LoanOut: """LoanOut inkl. Überfälligkeit und – für die Übersicht – Stück und Artikel.""" out = LoanOut.model_validate(loan) out.days_overdue = tage_ueberfaellig(loan, heute) out.username = loan.user.username if loan.user else None item = loan.item if item is not None: out.item_uid = item.uid out.product_id = item.product_id out.location_id = item.location_id out.location_name = item.location.name if item.location else None if item.product is not None: out.product_name = item.product.name out.product_brand = item.product.brand return out