This commit is contained in:
Bastian Wagner
2026-07-20 09:01:36 +02:00
parent 8cf57d7878
commit e62673ac11
98 changed files with 17372 additions and 80 deletions

View File

@@ -0,0 +1,123 @@
# HausPilot: Authentifizierungs- und Benutzerintegration
## Ergebnis der Boilerplate-Analyse
HausPilot verwendet unveraendert die vorhandene Backend-for-Frontend-
Authentifizierung. Das Boilerplate spricht keinen bestimmten Hersteller fest,
sondern einen ueber `OIDC_ISSUER` konfigurierten OpenID-Connect-Provider. Der
Login ist ein OIDC Authorization Code Flow mit PKCE, `state` und `nonce`.
Der Browser startet den Login ueber `GET /api/auth/login`. Der Callback liegt
fest unter `<APP_BASE_URL>/api/auth/callback`. Der Backend-Callback validiert
Issuer, Audience, Signaturalgorithmus, Nonce und Subject, laedt gegebenenfalls
UserInfo und legt den lokalen Benutzer an oder aktualisiert ihn. Die stabile
Identitaet beim Provider ist `(issuer, subject)`; fachliche Beziehungen verwenden
ausschliesslich die interne UUID `users.id`.
OIDC Access-, Refresh- und ID-Tokens werden verschluesselt in der MySQL-Tabelle
`sessions` gespeichert. Der Browser erhaelt nur eine signierte, HttpOnly
Session-ID und ein lesbares CSRF-Cookie. Schreibende Requests senden dieses
Cookie ueber den vorhandenen `csrfInterceptor` als `X-CSRF-Token`. Idle- und
Absolute-Timeout werden beim serverseitigen Session-Resolve geprueft. Es gibt
keinen zweiten Token-Refresh im Browser.
Der globale `PermissionsGuard` loest die Session auf, prueft den globalen
Benutzerstatus und setzt `AuthenticatedRequest.user` mit interner UUID,
Session-ID und effektiven Permissions. Controller verwenden weiterhin
`@RequirePermissions(...)`. Deaktivierte Benutzer werden beim Login und bei
jeder Session-Aufloesung abgewiesen; ihre historischen fachlichen Beziehungen
werden nicht geloescht.
Angular ermittelt den aktuellen Benutzer ueber `AuthService.ensureLoaded()` und
`GET /api/me`. Der Auth-State liegt ausschliesslich in dessen Signals. Der
vorhandene `permissionGuard` steuert nur Navigation und Darstellung; das Backend
bleibt verbindlich. Logout erfolgt ausschliesslich ueber `/api/auth/logout`,
widerruft die lokale Session, loescht Session- und CSRF-Cookie und verwendet
danach den OIDC `end_session_endpoint` beziehungsweise `OIDC_LOGOUT_URL`.
## Registrierung und Benutzerprofil
Das Boilerplate besitzt keinen lokalen Registrierungs- oder Passwortdialog und
keine lokale Passwort-Entity. Konten werden durch den vorhandenen Identity
Provider registriert oder bereitgestellt und beim ersten erfolgreichen OIDC-
Login lokal synchronisiert. HausPilot fuehrt daher keine zweite Registrierung
ein. Nach dem ersten Login werden offene Einladungen nur angezeigt, niemals
automatisch angenommen.
Die lokale `UserEntity` speichert UUID, Issuer, Subject, Anzeigename, E-Mail,
Aktivstatus, globale Rollen und Einstellungen. Name und E-Mail werden bei jedem
Login aus dem OIDC-Profil aktualisiert. Aktive Mitgliedschaften bleiben auch bei
einer E-Mail-Aenderung ueber `users.id` stabil. Offene Einladungen bleiben an die
normalisierte urspruengliche Zieladresse gebunden.
Die Integration uebernimmt ausserdem den standardisierten OIDC-Claim
`email_verified`. Eine Einladung kann nur angenommen werden, wenn der Provider
die aktuelle Zieladresse als verifiziert bestaetigt. Fehlt diese Bestaetigung,
wird die Annahme sicher abgewiesen.
## Globale und projektbezogene Autorisierung
Globale Rollen (`admin`, `user` und verwaltete globale Rollen) bleiben von den
HausPilot-Projektrollen getrennt. Eine globale Basis-Permission erlaubt nur die
Verwendung der Projekt-API. Sie erzeugt weder eine Mitgliedschaft noch einen
Zugriff auf ein privates Projekt. Insbesondere erhaelt ein globaler Administrator
keinen impliziten Projektzugriff.
Projektrollen sind `owner`, `administrator`, `editor` und `reader`. Jede
Projektabfrage prueft serverseitig eine aktive Mitgliedschaft und die fuer die
Aktion erforderliche Rolle. Unterentitaeten werden immer zusammen mit der
Projekt-ID geladen beziehungsweise geprueft. Eigentuermer-, Autor- und
Einladender-IDs sind keine DTO-Felder, sondern stammen aus dem vorhandenen
serverseitigen Current-User-Kontext.
Projektanlage, Eigentuemermitgliedschaft und Erstellungsaktivitaet werden in
einer Datenbanktransaktion gespeichert. Projektrollen entstehen nur durch diese
Projektanlage, explizite Einladungsannahme oder eine berechtigte Aenderung im
Projekt; OIDC-Gruppen und globale Rollen werden nicht abgebildet.
## Einladungen und sicherer Rueckkehrpfad
Einladungen speichern die normalisierte E-Mail-Adresse und optional die bekannte
interne Benutzer-ID. Der zufaellige Einladungstoken wird nur im Link ausgegeben;
in MySQL liegt ausschliesslich sein SHA-256-Hash. Annahme und Ablehnung verlangen
eine gueltige bestehende Session. Das Backend prueft Status, Ablauf,
Widerruf, Zieladresse, `email_verified`, aktiven Benutzer und eine noch nicht
bestehende aktive Mitgliedschaft. Fehlermeldungen geben nur eine maskierte
Adresse aus.
Ist ein Besucher nicht angemeldet, verweist Angular auf den bestehenden
`/api/auth/login`-Flow. Der gewuenschte Rueckweg wird serverseitig im kurzlebigen
OIDC-Login-State gehalten. Erlaubt sind nur interne Pfade; Schemes,
Protokoll-relative URLs und Backslashes werden verworfen. Nach dem Callback baut
das Backend das Ziel relativ zu `FRONTEND_BASE_URL` auf. Tokens oder OIDC-Daten
werden nie in Rueckkehrparametern gespeichert.
## Benachrichtigungen, Mail und Cleanup
Das vorhandene `NotificationsService` erzeugt fuer bereits bekannte aktive
Benutzer eine persoenliche In-App-Benachrichtigung. Normale Endpunkte bleiben auf
den Session-Benutzer beschraenkt. Das Boilerplate hat keinen Mail-Service, keine
Templates, Queue oder Retry-Jobs. HausPilot fuehrt keine parallele
Mail-Infrastruktur ein; der Versandstatus einer Einladung wird daher explizit
als `not_configured` gespeichert. Der Einladungsdatensatz bleibt konsistent und
kann spaeter an eine architektonisch beschlossene Mail-Komponente angebunden
werden.
Projektseiten halten Daten nur komponentenlokal. Bei 401 setzt der vorhandene
globale Auth-State den Benutzer zurueck; dadurch werden geschuetzte Seiten
entfernt und das bestehende Notification-Polling samt personenbezogenem Cache
gestoppt. Ein regulaerer Logout verlaesst die SPA und startet sie ohne alten
In-Memory-State neu. HausPilot fuehrt keine Live-Verbindungen ein.
## Relevante Konfiguration
- OIDC: `OIDC_ISSUER`, `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET`,
`OIDC_SCOPES`, `OIDC_ALLOWED_ALGORITHMS`, optional `OIDC_LOGOUT_URL`
- Redirects: `APP_BASE_URL`, `FRONTEND_BASE_URL`
- Session: `SESSION_COOKIE_NAME`, Idle-/Absolute-Timeout,
`SESSION_SECRET`, `SESSION_ENCRYPTION_KEY`
- Browser-Schutz: `CORS_ORIGINS`, `CSRF_HEADER_NAME`, Secure/SameSite-Cookies,
Helmet, Rate Limits und Logging-Redaction
Es wurde keine parallele Authentifizierungs-, Benutzer-, Passwort-, Token-,
Registrierungs-, Mail-, Queue- oder Live-Connection-Infrastruktur eingefuehrt.

215
docs/hauspilot-domain.md Normal file
View File

@@ -0,0 +1,215 @@
# HausPilot-Fachmodule
## MVP-Abschluss 2026-07
### Neue Migration und Datenmodelle
`1720000005000-AddMentionsAndReminders.ts` ergänzt `task_comment_mentions`, `reminder_deliveries` und `applied_project_templates`. Eindeutige Constraints deduplizieren Benutzer pro Kommentar, Reminder-Schlüssel sowie Vorlagen je Projekt und Ziel. Die Tabellen referenzieren weiterhin ausschließlich bestehende Benutzer- und Projekt-UUIDs.
### Listen-API
Räume, Aufgaben, Meilensteine, Ausgaben, Dokumente, Kommentare und Aktivitäten liefern serverseitig `{ items, page, pageSize, totalItems, totalPages }`. `pageSize` ist auf 100 begrenzt. Filter enthalten stets `projectId`. Sortierfelder sind DTO-Whitelists und werden nie als frei übergebener SQL-Ausdruck ausgeführt. Aufgaben unterstützen Suchtext, mehrere Statuswerte, Priorität, Raum, Verantwortlichen, Kategorie, Datumsbereiche, überfällig, blockiert, nicht zugewiesen und „nur meine“. Ausgaben und Dokumente besitzen Zuordnungs- und Datumsfilter.
Neue gezielte Leseendpunkte sind `GET /projects/:projectId/tasks/:taskId` und `GET /projects/:projectId/calendar?from=YYYY-MM-DD&to=YYYY-MM-DD`. Die stabile Angular-Route `/projekte/:projectId/aufgaben/:taskId` enthält Stammdaten, Schnellstatus, Checkliste, Vorgänger und Nachfolger, Kommentare, Erwähnungen, Dokumente und den Aktivitätsauszug. Der Zeitplan bietet eine responsive Monats- und Agendaansicht ohne neue UI-Bibliothek und lädt nur den benötigten Zeitraum.
### Vorlagen
Die unveränderlichen Systemvorlagen „Reihenhaus-Renovierung“, „Schlüsselübergabe“, „Renovierung eines Raums“ und „Umzug“ werden transaktional kopiert. Die Reihenhausvorlage enthält fünf Bereiche, 15 Räume, 13 Budgetkategorien, Meilensteine und allgemeine Aufgaben. Die Raumvorlage erzeugt 14 Aufgaben samt Abhängigkeitskette. `applied_project_templates` erkennt Wiederholungen je Projekt und Zielraum; erneute Anwendung verlangt `confirmDuplicate`. Fehler rollen alle Kopien und die Aktivität gemeinsam zurück.
### Strukturierte Erwähnungen
Kommentare übertragen neben sichtbarem `@Anzeigename` eine validierte Liste stabiler Benutzer-UUIDs. Nur aktive Mitglieder desselben Projekts werden akzeptiert. Mehrfachnennungen werden dedupliziert, Selbst-Erwähnungen erzeugen keine Nachricht und beim Bearbeiten werden nur neu hinzugekommene Benutzer benachrichtigt. Die In-App-Nachricht verweist direkt auf Aufgabe und Kommentar.
### Scheduler und Reminder
Der offizielle Nest-Scheduler führt den `ReminderService` standardmäßig alle 15 Minuten aus. Er erfasst bald fällige und überfällige Aufgaben, Meilensteine, überfällige offene Ausgaben sowie bald ablaufende Einladungen mit bekanntem Benutzer. Entfernte oder global deaktivierte Mitglieder erhalten nichts. `reminder_deliveries.dedupe_key` enthält Reminder-Art, Entität, Empfänger und Bezugsdatum; der Unique Constraint schützt auch parallele Läufe. Eine Datumsänderung erzeugt einen neuen fachlich relevanten Schlüssel.
- `REMINDER_INTERVAL_MS`: mindestens 60.000, Standard 900.000
- `REMINDER_DUE_SOON_DAYS`: Standard 3
### Development-Seed
Nach Migrationen und mindestens einem vorhandenen aktiven SSO-Benutzer wird der ausschließlich explizite Seed gestartet:
```text
npm run seed:development
```
Er erzeugt idempotent „Umzug Reihenhaus“ mit fünf Etagen, 15 Räumen, 30 Aufgaben, zehn Abhängigkeiten, 20 Checklistenpunkten, zehn Kommentaren, acht Meilensteinen, 13 Budgetkategorien, 15 Ausgaben, vier Dokumentmetadaten, 20 Aktivitäten, In-App-Benachrichtigungen und sofern vorhanden bis zu vier bestehende Benutzer in unterschiedlichen Rollen. Er legt weder Benutzer noch Passwörter an. In `NODE_ENV=production` verweigert er Ausführung und Reset. Der gezielte Reset entfernt nur das eindeutig markierte Seed-Projekt:
```text
npm run seed:development -- --reset
```
Seed-Dokumente enthalten absichtlich nur Metadaten, keine fingierten Binärdateien.
### Abhängigkeitssicherheit und Grenzen
Die produktiven Advisories wurden ohne Major-Upgrade geschlossen: `@nestjs/swagger` 11.4.6 und TypeORM 0.3.31 beheben die gemeldeten transitiven Risiken in `js-yaml`, `lodash`, `path-to-regexp` und TypeORM. `npm audit --omit=dev` meldet danach keine bekannte Schwachstelle. `@nestjs/schedule` 6.1.3 (MIT, Nest-10/11-kompatibel) ist die einzige neue Laufzeitabhängigkeit.
Der Scheduler läuft pro Backend-Instanz; der Datenbank-Constraint macht die Auslieferung mehrfach laufender Instanzen idempotent, ersetzt bei sehr großen Installationen jedoch kein verteiltes Job-Leasing. Komplexe Gantt-, Offline-, E-Mail- und externe Kalenderfunktionen bleiben außerhalb des MVP.
## Ausgangsarchitektur und Wiederverwendung
Die Umsetzung erweitert die bestehende Angular-/NestJS-Anwendung. Globale Anmeldung, OIDC, serverseitige MySQL-Session, CSRF, `PermissionsGuard`, `ProjectAccessService`, `ProjectMembershipEntity`, `NotificationsService` und `ProjectActivityEntity` bleiben die einzigen Mechanismen für Identität, Projektzugriff, Benachrichtigungen und Aktivitäten. Es gibt keine zweite Benutzer-, Login-, Session- oder Projektverwaltung.
Die fachlichen Controller tragen weiterhin `@RequirePermissions(Permission.ProjectsUse)`. Danach prüft `ProjectAccessService` die aktive Mitgliedschaft und die Projektrolle. Jede Unterentität wird zusätzlich mit der Kombination aus `id` und `projectId` geladen. Dadurch reicht weder eine gültige Anmeldung noch eine fremde Entitäts-ID zum Zugriff.
## Datenmodell und Module
`RenovationModule` bündelt die eng zusammenhängende Renovierungsdomäne und besteht aus Controller, Service, Persistence-Repository, Dokumentablage und DTO-Validierung. Die Migration `1720000004000-AddRenovationDomain.ts` ergänzt:
- Gebäude, Etagen und Räume
- Renovierungsaufgaben, Checklisten, Abhängigkeiten und Kommentare
- Meilensteine
- Budgetkategorien und Ausgaben
- projektbezogene Dokumentmetadaten
- Projektstatus, Währung und Gesamtbudget-Grundlage
Historische Benutzerbeziehungen verwenden die bestehende interne User-UUID. Beim Entfernen eines Mitglieds werden offene Zuweisungen in derselben Transaktion geleert; abgeschlossene und historische Beziehungen bleiben erhalten.
## API
Die API liegt unter `/api/projects/:projectId` und stellt Listen sowie Create-/Patch-/Delete-Endpunkte für `buildings`, `floors`, `rooms`, `tasks`, `milestones`, `budget-categories`, `expenses` und `documents` bereit. Aufgaben besitzen zusätzlich `checklist`, `dependencies` und `comments`. Weitere gezielte Endpunkte sind:
- `GET /projects/:projectId/dashboard`
- `GET /projects/:projectId/activities`
- `GET /templates`
- `POST /projects/:projectId/apply-template/:templateId`
- `GET /projects/:projectId/documents/:id/download`
Request-DTOs enthalten weder Ersteller noch handelnde Benutzer. Diese IDs stammen ausschließlich aus dem bestehenden Sessionkontext.
## Rollen
- Eigentümer und Projektadministratoren verwalten die gesamte Struktur, Budgets und Vorlagen.
- Bearbeiter verwalten Räume, Aufgaben, Checklisten, Kommentare, Ausgaben und Dokumente.
- Leser können ausschließlich lesen.
Budgetkategorien sind bewusst Eigentümern und Projektadministratoren vorbehalten, weil ihre Änderung den finanziellen Projektrahmen verändert. Globale SSO-Rollen erzeugen weiterhin keine Projektmitgliedschaft.
## Fortschritt und Abhängigkeiten
Die einzige Fortschrittsberechnung liegt in `renovation/progress.ts`: Idee 0, geplant 10, beauftragt 25, in Arbeit 50, blockiert 25, Abnahme 90 und erledigt 100 Prozent. Entfallene Aufgaben werden ausgeschlossen. Das optionale positive Gewicht bildet einen gewichteten Mittelwert; ohne fachliches Gewicht wird `1` verwendet.
Die zentrale Zykluserkennung erweitert den gerichteten Graphen probeweise um die neue Kante und führt eine Tiefensuche mit `visiting`-/`visited`-Mengen aus. Selbstbezüge sowie direkte und indirekte Zyklen werden mit HTTP 409 abgewiesen. Eine Aufgabe ist blockiert, sobald ein zwingender Vorgänger nicht erledigt ist; der Start wird dann serverseitig verhindert.
## Dashboard und Budget
Der Dashboard-Endpunkt berechnet aggregiert Gesamtfortschritt, offene, überfällige, blockierte, kritische, eigene und nicht zugewiesene Aufgaben, Raumstatus, nächste/gefährdete Meilensteine, aktive Mitglieder sowie geplante, tatsächliche, offene und bezahlte Kosten. Handlungsorientierte Hinweise werden serverseitig aus denselben Daten erzeugt.
Geldbeträge werden in MySQL als `DECIMAL` gespeichert und erst für Aggregationen explizit in Zahlen überführt. Stornierte Ausgaben fließen nicht in tatsächliche Kosten ein.
## Optimistische Nebenläufigkeit
Gebäude, Etagen, Räume, Aufgaben, Meilensteine, Budgetkategorien, Ausgaben und Dokumentmetadaten besitzen eine Versionsnummer. Updates verwenden atomar `WHERE id = ? AND project_id = ? AND version = ?` und erhöhen die Version in derselben SQL-Anweisung. Ein veralteter Stand überschreibt daher keine neueren Daten und liefert HTTP 409. Angular zeigt dafür einen konkreten Konflikthinweis und führt keine verlustbehaftete automatische Zusammenführung durch.
## Dokumentablage
Dateien werden nicht öffentlich ausgeliefert. Upload und Download laufen nach Projektzugriffsprüfung über das Backend. Erlaubt sind PDF, JPEG, PNG und WebP bis zum konfigurierten Limit. Endung, MIME-Typ und Dateisignatur müssen zusammenpassen; der Speichername ist eine zufällige UUID und Pfadbestandteile aus dem Client werden nie übernommen. Metadaten liegen in MySQL, Inhalte unter `DOCUMENT_STORAGE_PATH`.
Konfiguration:
- `DOCUMENT_STORAGE_PATH` (Standard `storage/documents`)
- `DOCUMENT_MAX_FILE_SIZE_BYTES` (Standard 10 MiB)
Für mehrere Backend-Instanzen muss der Pfad als gemeinsames, geschütztes Volume bereitgestellt werden. Virenscanning und objektbasierter Cloud-Speicher sind sinnvolle Produktionsausbaustufen.
## Frontend
Der responsive Projektarbeitsbereich nutzt ausschließlich bestehende Design-Tokens und UI-Komponenten. Die horizontale, tastaturbedienbare Projektnavigation umfasst Übersicht, Räume, Aufgaben, Zeitplan, Budget, Dokumente, Aktivitäten und die bestehende Mitgliederseite. Listen wechseln auf kleinen Displays in Karten; Hauptaktionen benötigen kein Hover.
## Vorlagen und Erweiterbarkeit
Systemvorlagen werden als unveränderliche Definitionen angeboten. Die Raumrenovierung kopiert Aufgaben und Abhängigkeiten; ein vorhandener Aufgabenbestand verlangt eine explizite Duplikatbestätigung. Die Servicegrenze ist für weitere transaktionale Vorlagen vorbereitet.
Spätere Module für Kartons/QR-Codes, Inventar, Lieferanten/Angebote, Materialmengen, Rechnungserkennung, KI-Planung, E-Mail-Erinnerungen und Kalenderintegration können über `projectId`, bestehende Mitgliedschaften, Dokumentreferenzen, Aktivitäten und Benachrichtigungen angebunden werden, ohne Authentifizierung oder Projektzugriff zu duplizieren.
## Tests
Die Verhaltenstests decken insbesondere die gewichtete Fortschrittsberechnung, ausgeschlossene Aufgaben, direkte/indirekte Abhängigkeitszyklen, kombinierte Aufgabenfilter und den Concurrency-Hinweis ab. Bestehende Tests sichern SSO, Projektzugriff, Einladungen, Sessions und Benachrichtigungen weiterhin ab.
## Frühere MVP-Grenzen
Die zuvor fehlenden Vorlagen, strukturierten Erwähnungen und deduplizierten Reminder wurden mit dem oben dokumentierten MVP-Abschluss umgesetzt. Eine Queue wurde dafür bewusst nicht eingeführt.
# Möbel- und Einrichtungsplanung
## AG-Grid-Arbeitsoberfläche
Die Möbelverwaltung nutzt `ag-grid-angular` und `ag-grid-community` 36.0.1 (MIT, Community
Edition). Registriert wird ausschließlich `AllCommunityModule`. Enterprise-Funktionen wie Row
Grouping und Master/Detail werden weder importiert noch vorausgesetzt. Bedarfe, Alternativen,
Bestellungen und Szenarien sind getrennte Grid-Ansichten; die vorhandenen Formulare bleiben für
die vollständige Detailbearbeitung zuständig.
Listen verwenden die bestehende Page-Antwort (`items`, `page`, `pageSize`, `totalItems`,
`totalPages`). Bedarfe unterstützen zusätzlich `openDecision` und `overBudget`; die projektweite
Alternativenliste unterstützt `roomId`, `requirementId`, `status`, `availability`, `favorite`,
`selected`, `ordered` und `delayed`. Sortierfelder werden im Service gegen Positivlisten geprüft.
Bedarfsantworten enthalten aggregierte Optionen, Preise, Budgetabweichung, Bestellstatus und
Lieferdatum. Die Repository-Abfragen laden Optionen gebündelt für die aktuelle Seite.
Inline-Änderungen senden die vorhandene Versionsnummer. Bei Fehlern wird der alte Zellwert
wiederhergestellt; bei HTTP 409 wird der Serverstand neu geladen und der Konflikt sichtbar gemeldet.
Favorit und Auswahl verwenden die bestehenden Fachendpunkte. Leser erhalten keine Editoren; die
serverseitige Projektberechtigung bleibt verbindlich.
Spaltenzustand wird ohne Fachdaten lokal unter Benutzer-, Projekt- und Ansichtsschlüssel gespeichert.
Die Suche wird um 300 ms entprellt, Row-IDs sind stabile Datenbank-IDs und CSV-Export nutzt die
Community-Funktion. Auf kleinen Displays gelten eine reduzierte Höhe und die vorhandene
Dialogbearbeitung für komplexe Eingaben.
Die Möbelplanung ist Bestandteil des vorhandenen `RenovationModule` und verwendet dessen
`ProjectAccessService`, Dokumentablage, Ausgaben, Aktivitäten und Benachrichtigungen. Ein
`FurnitureRequirement` beschreibt den Bedarf eines Raums; konkrete Produkte werden als
`FurnitureOption` gespeichert. `FurnitureScenario` und `FurnitureScenarioSelection`
kombinieren höchstens eine Alternative je Bedarf zu vergleichbaren Einrichtungsvarianten.
## Preis- und Kostenregeln
Der geplante Gesamtpreis lautet `Einzelpreis × Menge + Versand + Zusatzkosten Rabatt`.
Bei vorhandenem Mobiliar entfallen Anschaffungskosten; Umzug und Aufbereitung werden dennoch
berücksichtigt. Alle persistierten Geldwerte sind `DECIMAL(13,2)` und werden im Backend als
Strings verarbeitet; die zentrale Berechnung in `furniture-pricing.ts` rechnet in Cent.
Planwerte stammen aus der ausgewählten Alternative bzw. einem Szenario. Ist-Kosten stammen
ausschließlich aus nicht stornierten, über `furniture_option_id` verknüpften Ausgaben. Damit
wird eine Bestellung nicht zugleich als Produktpreis und Ausgabe doppelt gezählt.
## API und Listenmodell
Die Endpunkte liegen unter `/projects/:projectId/furniture-requirements`,
`/furniture-options`, `/furniture-scenarios`, `/furniture-summary` sowie
`/rooms/:roomId/furniture-summary`. Bedarfslisten unterstützen `page`, `pageSize` (maximal
100), `search`, Raum, Kategorie, Status, Priorität, Verantwortlichen, Favorit, Auswahl,
Bestell-/Lieferstatus und Lieferverzug. Erlaubte Sortierungen sind Name, Raum, Kategorie,
Preis, Priorität, Status, Lieferdatum, Änderungsdatum und Sortierreihenfolge.
## Auswahl, Szenarien und Nebenläufigkeit
Das Auswählen eines Produkts sperrt die betroffene Alternative und deren Bedarf
transaktional, prüft die Version, entfernt die bisherige Auswahl und erzeugt eine Aktivität.
Damit bleibt auch bei parallelen Anforderungen höchstens eine aktive Auswahl bestehen. Bedarfe,
Alternativen und Szenarien besitzen Versionsnummern; veraltete Änderungen liefern HTTP 409.
Automatische Szenarien wählen günstigste, bevorzugte (Auswahl, Favorit, günstigste), teuerste
oder vorhandene Alternativen und können danach manuell verändert werden.
## Dokumente, Ausgaben, Status und Sicherheit
`furniture_option_documents` referenziert ausschließlich geschützte Projektdokumente; es
existiert keine zweite Dateiablage. Ausgaben können Bedarf und Alternative referenzieren.
Sämtliche Unterentitäten werden zusätzlich zur authentifizierten Projektmitgliedschaft gegen
die Pfad-`projectId` geprüft. Leser dürfen lesen und vergleichen; Eigentümer,
Administratoren und Bearbeiter verwenden die bestehende Projektaktion `edit`.
Bestellungen speichern Besteller, Nummer und Liefertermin. Teil- und Komplettlieferung sowie
Verspätung werden ausgewertet. Liefererinnerungen laufen über den vorhandenen Scheduler und
die bestehende `ReminderDelivery`-Deduplizierung; eine Terminänderung erzeugt durch das Datum
im Schlüssel eine neue, wiederholte Jobläufe dagegen keine weitere Benachrichtigung.
## Migration und Development-Seed
Migration `1720000007000-AddFurniturePlanning` erstellt die Möbel- und Szenariotabellen und
ergänzt optionale Expense-Referenzen. Der Development-Seed bleibt produktionsgesperrt und
legt 16 Bedarfe, 48 Alternativen, drei Szenarien, Favoriten, Auswahlen, Bestellungen, eine
Lieferung, einen Lieferverzug, sichere Dokumentmetadaten und verknüpfte Ausgaben an. Die
Etagen des Seeds sind Keller, Erdgeschoss, 1. Stock und Dachboden.