Inventory API
Die Inventory API steuert Inventur-Sessions (Stocktake): Beim Start wird ein Snapshot der erwarteten Assets gezogen, anschließend werden Assets per QR-/Tag-Scan erfasst. Aus Soll (Snapshot) und Ist (Scans) ergeben sich fehlende und unerwartete Assets. Scope ist entweder ein Standort (alle Assets einer Location) oder ein Benutzer (dessen zugewiesene Assets).
Lebenszyklus
Start (Snapshot erwarteter Assets) │ POST /api/inventory-sessions ▼ ACTIVE ──scannen──▶ POST /:id/scan (pro Asset, unique je Session) │ POST /:id/ready-for-review (Scanner übergibt zur Prüfung) ▼ READY FOR REVIEW │ POST /:id/complete (Agent schließt ab → endedAt gesetzt) ▼ COMPLETED → Notification INVENTORY_SESSION_COMPLETED an Ersteller
rescanBehavior: OVERWRITE (Default) — ein erneuter Scan desselben Assets überschreibt den vorherigen (ein Eintrag je Session+Asset). ACCUMULATE — Mehrfach-Scans addieren die Mengen (für Verbrauchsmaterial/Consumables).
Authentifizierung & Permissions
Self-Service ist eingebaut: Endbenutzer dürfen eine Session für SICH starten, scannen und zur Prüfung übergeben — aber nicht abschließen oder fremde Sessions sehen. Abschluss, Scan-Korrekturen und Reports sind Agent-/Admin-Rechte. Permissions & RBAC.
| Permission | Beschreibung |
|---|---|
inventory.startSession | Session für andere/Standort starten |
inventory.startOwnSession | Eigene Session starten (Self-Service, Default an) |
inventory.scan | Assets scannen + Scan-Notiz, ready-for-review |
inventory.viewSessions / viewOwnSessions | Alle bzw. nur eigene Sessions sehen |
inventory.completeSessions | Session abschließen |
inventory.editScans | Scans korrigieren/löschen |
inventory.viewReports / exportReports | Report ansehen / exportieren |
inventory.viewDeleted / restore | Papierkorb sehen / Session wiederherstellen (beide Default aus) |
Zum Wiederherstellen sind zwei Rechte nötig: inventory.restore für die Aktion selbst und inventory.viewDeleted, um den Papierkorb zu sehen — wer den Papierkorb nicht sehen darf, holt auch nichts daraus zurück. Zusätzlich gilt dieselbe Sichtbarkeit wie in der Liste: Wiederherstellen lässt sich nur, was man auch sehen darf. Beide Rechte sind standardmäßig aus; nur die Administrator-Systemrolle erhält sie automatisch.
Endpoints Übersicht
Mount: /api/inventory-sessions
| Method | Endpoint | Beschreibung | Permission |
|---|---|---|---|
POST | / | Session starten (Snapshot erwarteter Assets) → 201, Session-Objekt | startSession / startOwnSession |
GET | / | Sessions auflisten — { data, pagination, counts }; ?q, ?page/per, ?sort, ?f.<feld>, ?status=active|completed, ?deleted=1 | viewSessions / viewOwnSessions |
GET | /:id | Session-Detail | view… |
PATCH | /:id | Session-Metadaten ändern | startSession |
POST | /:id/ready-for-review | Zur Prüfung übergeben | scan |
POST | /:id/complete | Session abschließen (endedAt) | completeSessions |
DELETE | /:id | Session in den Papierkorb legen (Soft-Delete, auch abgeschlossene; Scans bleiben) → 204 | startSession / startOwnSession (nur eigene Session: gestartet oder Ziel-Benutzer) |
POST | /:id/restore | Session aus dem Papierkorb zurückholen | restore + viewDeleted |
POST | /:id/scan | Asset scannen — assetId ODER code (gescannter QR-/Barcode-Wert), dazu quantity, note | scan |
PATCH | /:id/scans/:scanId | Scan korrigieren (Menge) | editScans |
PATCH | /:id/scans/:scanId/note | Scan-Notiz setzen | scan |
DELETE | /:id/scans/:scanId | Scan löschen → 204 | editScans |
GET | /:id/scans | Scans der Session — { data, pagination } | view… |
GET | /:id/scans/stats | Scan-Statistik | view… |
GET | /:id/unexpected | Gescannt, aber nicht erwartet — { data, pagination } | view… |
GET | /:id/missing | Erwartet, aber nicht gescannt — { data, pagination } | view… |
GET | /:id/report | Inventur-Report (Soll-Ist) | viewReports |
Felder
InventorySession
| Feld | Typ | Beschreibung |
|---|---|---|
name | String | z.B. „Inventur Q4 2026 – Serverraum" |
scopeType | enum | LOCATION, USER |
locationId | String? | bei Scope LOCATION (Asset-Standort) |
userId | String? | bei Scope USER (dessen Assets) |
expectedAssetIds | String[] | Snapshot der Soll-Asset-IDs beim Start |
expectedSnapshot | JSON | Vollständiger Soll-Snapshot (assetTag, name, quantity …) |
rescanBehavior | enum | OVERWRITE (default), ACCUMULATE |
readyForReviewAt / By | DateTime? / String? | Übergabe zur Prüfung |
dueAt | DateTime? | Frist für die Inventur — setzen und ändern darf sie nur inventory.startSession (403 INVENTORY_DUE_AT_FORBIDDEN); der Ziel-Benutzer kann seine eigene Frist also nicht verschieben |
deletedAt | DateTime? | gesetzt, solange die Session im Papierkorb liegt |
startedBy / startedAt / endedAt | — | Ersteller, Start, Abschluss (endedAt = abgeschlossen) |
Fristen-Überwachung: Ein Built-in-Cronjob („Inventory Due Monitoring", stündlich, standardmäßig aktiv) meldet nahende und überschrittene Fristen: drei Tage und einen Tag vorher, danach überfällig. Jeder Meilenstein wird nur einmal gemeldet. Empfänger ist der Ziel-Benutzer bzw. der Starter der Session — bei Überfälligkeit zusätzlich der Starter. Ohne gesetztes dueAt passiert nichts. CronJobs API.
InventoryScan
| Feld | Typ | Beschreibung |
|---|---|---|
assetId | String | Gescanntes Asset (unique je Session — siehe rescanBehavior) |
quantity | Int | Gezählte Menge (Default 1) |
expectedQty | Int? | Erwartete Menge aus dem Snapshot |
scanCount | Int | Wie oft dieses Asset in der Session gescannt wurde |
note | String? | Notiz zum Scan |
scannedBy / scannedAt | — | Wer/wann gescannt |
Fehlende Assets, Live-Status & Auto-Account
- Statischer Missing-Snapshot: Das Soll bleibt unverändert (Inventur-Protokoll) — ein während der Session z.B. als LOST gemeldetes Asset bleibt im Missing-Tab und -Zähler.
- Live-Status (currentStatus): Beim Lesen werden Missing-Zeilen mit dem aktuellen Asset-Status annotiert („inzwischen LOST / zurückgenommen"); undefined = Asset inzwischen gelöscht.
- Automatische Erfassung (Auto-Account): Eine nicht gescannte Komponente gilt als automatisch erfasst, wenn ihr Container in der Session gescannt wurde (auch über mehrere Ebenen, über die Relationen PART_OF/INSTALLED_ON). Die Komponente wird dabei nur als erfasst markiert; ein Scan wird nicht angelegt. Komponenten ohne Container-Relation bleiben fehlend. Komponenten außer Betrieb (LOST/RETIRED/DISPOSED) werden NICHT automatisch erfasst — sie bleiben fehlend und tragen ein currentStatus-Badge.
Dadurch trennt GET /:id/scans/stats echte von auto-erfassten Missing: { scanned, expected, missing, autoAccounted, unexpected }. GET /:id/missing liefert missing (nur ECHTE Missing) plus autoAccountedAssets; jede Missing-Zeile trägt currentStatus und ggf. autoAccountedVia.
Asset-Bewegungen aus der Inventur
Die Inventur-Routen bewegen KEINE Assets — es gibt keinen Bewegungs-Endpoint unter /api/inventory-sessions. Aktionen an gescannten, unerwarteten oder fehlenden Assets laufen über die Asset-Flow-Endpunkte (es gelten die assets.*-Rechte, nicht inventory.*). Welche Aktion angeboten wird, hängt von Session-Ziel, Tracking-Modus und Status des Assets ab; Zuweisungen laufen dabei immer über Ausgabe, Installation oder Rücknahme:
| Kontext | Aktion | API |
|---|---|---|
| Unerwartet · USER-Scope · PERSON · user-los | An {User} ausgeben | POST /assets/:id/checkout |
| Unerwartet · USER-Scope · PERSON · IN_USE (anderer User) | Besitzer wechseln | checkin + checkout (+ Komponenten via coReturn/coMove) |
| Unerwartet · LOCATION-Scope · LOCATION AVAILABLE | Hier installieren | POST /assets/:id/install |
| Unerwartet · LOCATION-Scope · IN_USE woanders | Standort korrigieren | PATCH /assets/:id { locationId, coLocateAssetIds? } |
| Fehlend (beide Scopes) | Als verloren melden | PATCH /assets/:id { status: LOST, note } |
| PERSON IN_USE | Zurücknehmen | POST /assets/:id/checkin |
Sperr-Zustände: bei offener Übergabe (PENDING_ACCEPTANCE / RETURN_PENDING) ist nur „Detail öffnen" möglich. Details zu den Flows: Asset-Lebenszyklus.
Beispiel-Flow
# 1. Start a session for a location
POST /api/inventory-sessions
{ "name": "Stocktake Q4 2026 – Server Room", "scopeType": "LOCATION", "locationId": "clx-loc-id", "rescanBehavior": "OVERWRITE" }
# 2. Scan asset (QR → assetId)
POST /api/inventory-sessions/:id/scan
{ "assetId": "clx-asset-id", "quantity": 1, "note": "Rack 3" }
# 3. Hand off for review
POST /api/inventory-sessions/:id/ready-for-review
# 4. Check expected vs actual
GET /api/inventory-sessions/:id/missing # expected, not scanned
GET /api/inventory-sessions/:id/unexpected # scanned, not expected
# 5. Agent completes
POST /api/inventory-sessions/:id/complete