Ka-Note/docs/feature-inventory.md

187 lines
6.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Feature: Inventar
## Zweck
Hausinventar erfassen und verwalten: Gegenstände per Kamera oder Barcode aufnehmen,
KI erkennt Titel + Kategorie automatisch, Zuordnung zu Räumen und Familienmitgliedern.
Gesamtwert als Grundlage für Versicherungssumme.
---
## Räume
Räume werden in drei Gruppen eingeteilt (hardcodiert, nicht erweiterbar):
| `groupType` | Label | Beispiel-Default-Räume |
|---|---|---|
| `living` | Wohnbereiche | Wohnzimmer, Schlafzimmer, Kinderzimmer, Esszimmer, Flur |
| `functional` | Funktionsräume | Küche, Bad, WC, Arbeitszimmer, Hauswirtschaft |
| `outdoor` | Außen & Nebenbereiche | Keller, Dachboden, Garage, Garten, Abstellraum |
Default-Räume werden beim ersten Aufruf von `/inventory` automatisch angelegt
(`seedDefaultRoomsIfNeeded()`, Flag `'inventory.seeded'` in `settings`-Tabelle).
Räume sind löschbar (Soft-Delete). Jeder Raum hat ein `icon`-Feld (lucide icon name).
---
## Asset-Felder
| Feld | Typ | Beschreibung |
|---|---|---|
| `title` | string | Name des Gegenstands (KI-Vorschlag oder manuell) |
| `roomId` | string\|null | Zugeordneter Raum (null = ohne Raum) |
| `category` | string\|null | Aus hardcodierter Liste (s.u.) oder null |
| `status` | `draft`\|`complete` | Draft = Schnellerfassung, noch unvollständig |
| `condition` | `new`\|`good`\|`fair`\|`poor`\|null | Zustand |
| `brand` | string\|null | Marke |
| `model` | string\|null | Modell |
| `serialNumber` | string\|null | Seriennummer |
| `purchasePrice` | real\|null | Kaufpreis (€) |
| `purchaseYear` | integer\|null | Anschaffungsjahr |
| `notes` | string\|null | Freies Markdown-Notizfeld |
| `coverImageId` | string\|null | Primärbild (→ imageBlobs.id, Server-seitig) |
---
## Kategorien (hardcoded)
`null` (Keine Kategorie), Elektronik, Möbel, Kleidung, Schmuck, Kunstwerke,
Sportgeräte, Haushaltsgeräte, Haustechnik, Werkzeuge, Musikinstrumente,
Bücher & Medien, Spielzeug, Sammlerstücke, Gartengeräte, Fahrzeugzubehör, Sonstiges
Kein eigenes DB-Table — als TypeScript-Konstante in den jeweiligen Komponenten/Seiten.
---
## Erfassungs-Modi
### Foto-Modus (Schnellerfassung)
1. Raum wählen (optional via `?roomId=` Query-Parameter)
2. Loop: Foto aufnehmen → KI-Erkennung → Label + Kategorie bestätigen → Asset(draft) anlegen
3. [Fertig] → Dashboard
Route: `/inventory/capture/photo?roomId=...`
KI: POST `/api/vision/recognize` (OpenAI gpt-4o-mini oder Gemini gemini-1.5-flash)
### Barcode-Modus (Schnellerfassung)
1. Raum wählen (optional via `?roomId=` Query-Parameter)
2. Loop: EAN eingeben → UPCitemdb-Lookup → bestätigen → Asset(draft)
3. [Fertig] → Dashboard
Route: `/inventory/capture/barcode?roomId=...`
Lookup: POST `/api/vision/barcode` → UPCitemdb → Open Food Facts → EAN-Fallback
### Manuell (Einzel)
Raum wählen → Titel eingeben → Asset(draft) → Detailseite
Auch via CommandBar: `/asset Titel`
---
## Bilder-Architektur
**Inventory-Fotos werden NICHT lokal gecacht** (Grund: 500 Assets × ~300 KB = 150 MB).
Statt `storeImage()` (schreibt in Dexie): `uploadInventoryImage(blob)`:
1. Client komprimiert: JPEG, max-width 1920px, quality 0.75 → ~300 KB
2. POST `/api/inventory/images` → Server speichert in `imageBlobs`-Tabelle
3. Client erhält `imageId` zurück, speichert nur die ID im Asset
4. Anzeige: `getImageUrl(id)` fetcht live via `/api/sync/blob/{id}`
`storeImage()` aus `imageStore.ts` wird weiterhin für Editor-Paste-Bilder verwendet —
**nicht** für Inventory.
---
## Personen-Zuordnung
Assets können Familienmitgliedern zugeordnet werden (`assetPersons`-Tabelle, n:m).
Auswählbar sind nur Personen mit `contexts.type='person'` + `meta.personSubType='family'`.
Anzeige auf Detailseite als Chips.
---
## Vision-API-Key
Kein globaler API-Key — jeder User hinterlegt seinen eigenen Key in den Einstellungen
(Abschnitt "Bild-Erkennung"). Keys werden AES-256-GCM verschlüsselt in `user_settings`
gespeichert. Server-seitiges Verschlüsselungs-Secret: `SETTINGS_ENCRYPTION_KEY` in `.env`.
Rate-Limit: 100 Vision-Calls/Tag pro User (DB-basiert via `vision_usage`-Tabelle).
---
## Routen-Übersicht
| Route | Beschreibung |
|---|---|
| `/inventory` | Dashboard: Stats, Gesamtwert, Quick-Actions |
| `/inventory/rooms` | Räume-Grid (3-spaltig, gruppiert) |
| `/inventory/rooms/[id]` | Asset-Liste eines Raums |
| `/inventory/items` | Alle Assets (Suche, Filter, Grid/Liste) |
| `/inventory/capture/photo` | Foto-Schnellerfassungs-Loop |
| `/inventory/capture/barcode` | Barcode-Schnellerfassungs-Loop |
| `/inventory/[id]` | Asset-Detailseite |
---
## CommandBar-Integration
- Suche: Assets erscheinen in Ergebnissen (Badge `INVENTAR`, Icon 📦)
- `/inventar` → Dashboard
- `/asset [Titel]` → Asset anlegen (roomId=null) → Detailseite
---
## Server-Endpoints (Inventory-spezifisch)
```
POST /api/inventory/images Bild hochladen → { id }
POST /api/vision/recognize Foto → { label, category, candidates }
POST /api/vision/barcode EAN oder Barcode-Foto → { label, category }
GET /api/vision/settings { provider, apiKeySet, dailyUsage, dailyLimit }
PUT /api/vision/settings { provider?, apiKey? }
```
Sync läuft über den bestehenden `/api/sync/push` + `/api/sync/pull` Endpunkt.
Neue Tabellen im Sync: `rooms`, `assets`, `assetImages`, `assetPersons`.
---
## Wichtige Implementierungshinweise
### Bild-Upload vs. storeImage()
`storeImage()` und `uploadInventoryImage()` sind NICHT austauschbar:
- `storeImage()` → Dexie lokal + Sync (für Editor-Bilder)
- `uploadInventoryImage()` → direkt Server, kein Dexie (für Inventory-Fotos)
Verwechslung führt zu 75+ MB lokalem Storage.
### coverImageId und assetImages
`assets.coverImageId` = das Hauptbild (aus `imageBlobs.id`).
`assetImages` = weitere Bilder (ebenfalls `imageBlobs`-Referenzen).
Das Cover kann auf der Detailseite durch ein `assetImages`-Bild ersetzt werden
(`updateAsset({ coverImageId: assetImage.imageId })`).
### Draft vs. Complete
Assets beginnen immer als `draft`. Status `complete` muss manuell gesetzt werden.
Dashboard zeigt Gesamtzahl unabhängig vom Status.
`/inventory/items` filtert nach Status (Dropdown "Alle Status").
### Raum-Seed
`seedDefaultRoomsIfNeeded()` prüft `db.settings.get('inventory.seeded')`.
Wird nur einmal ausgeführt. Danach kann der User Räume löschen/umbenennen ohne Re-Seed.
### Barcode-Erkennung
Kein client-seitiger Barcode-Scanner.
Stattdessen: EAN manuell eingeben → POST `/api/vision/barcode`.
Server versucht UPCitemdb → Open Food Facts → EAN als Label.
---
## Bekannte Einschränkungen
- Kamera-Erfassung erfordert Online-Verbindung (kein offline capture)
- Barcode-Lookup max. 100/Tag (UPCitemdb free tier)
- Räume können nicht per Drag-Drop sortiert werden (`sortOrder` vorhanden, aber kein UI)
- Asset-Export (PDF/CSV) nicht implementiert (geplant als spätere Phase)
- Preisrecherche nicht implementiert (geplant: eBay Browse API)