17 Abnahmekriterien und 12 User Stories mit Status und Nachweis (Testskript, Route, Lane-Bericht); Soll-/Kann-Funktionen; getroffene MVP-Annahmen zu den offenen fachlichen Entscheidungen; bekannte Einschränkungen und nicht verifizierte Punkte. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
106 lines
12 KiB
Markdown
106 lines
12 KiB
Markdown
# Craftvia MVP – Abnahme-Matrix
|
||
|
||
> Stand: 2026-09-15 · Branch `feature/craftvia-mvp` @ `ef2f3f1` · Gate: tsc · lint · build · **65/65 Testskripte grün**; komplette Suite zusätzlich mit `RLS_ENFORCED=true` **65/65 grün**
|
||
> Quelle der Anforderungen: `docs/craftvia/SPEC-CRAFTVIA.md` §40 (Gesamt-Abnahme), §37 (User Stories), §44 (offene fachliche Entscheidungen).
|
||
> Status: **erfüllt** = umgesetzt und durch automatisierten Test belegt · **teilweise** = umgesetzt mit dokumentierter Lücke · **offen** = nicht umgesetzt.
|
||
> Nachweise verweisen auf Testskripte unter `scripts/` (Lauf: `npm run test`) und Lane-Berichte unter `docs/craftvia/lanes/`.
|
||
|
||
## 1. Gesamt-Abnahmekriterien (Spec §40)
|
||
|
||
| # | Kriterium | Status | Nachweis | Offene Punkte |
|
||
|---|---|---|---|---|
|
||
| 1 | Mehrere Mandanten mit sicher getrennter Datenhaltung | erfüllt | Tenant-Guard `src/server/db.ts` + RLS `enable_tenant_rls` für alle Fachtabellen; `test-tenant-isolation`, `test-rls-enforcement` (35+ Tabellen FORCE RLS), Mandantentrennungs-Fälle in allen Lane-Tests; `test-tenant-transaction` (Owner + RLS); **komplette Testsuite zusätzlich mit `RLS_ENFORCED=true` grün (52/52)** ; `test-e2e-*` Mandantentrennung über alle 35 Tenant-Modelle (direkt in Postgres + 67 Service-Aufrufe), `test-security-http` (Produktions-Build: fremde IDs in allen `/api/v1`- und Datei-Routen → 404) | RLS in Prod aktivieren (`RLS_ENFORCED=true`, Rolle `craftvia_app` LOGIN) – Betriebsschritt |
|
||
| 2 | Benutzer, Rollen und Montageteams verwaltbar | erfüllt | Fundament Nutzer/Rollen/Einladung (`test-invitation`, `test-tenant-users-authz`); Rollen tenant-admin/backoffice/team-lead/technician in `rbac.ts`; Teams L1 (`test-stammdaten-teams`) | |
|
||
| 3 | Auftragsbestätigungen als PDF importierbar | erfüllt | L3 `/imports`, `POST /api/v1/work-orders/import`; `test-import-flow`, `test-import-rules` | Live-Extraktion gegen Claude nur mit `ANTHROPIC_API_KEY` (`test-import-live`) |
|
||
| 4 | Kunden-, Objekt- und Auftragsdaten automatisiert extrahiert | teilweise | L3 Claude-Provider (PDF-/Bild-Block, JSON-Schema, Konfidenz je Feld), Plausibilitätsprüfung, Dubletten-/Objektkandidaten; Fake-Provider-Tests | Umgesetzt und mit Fake-Provider getestet; **Live-Extraktion mit Claude mangels `ANTHROPIC_API_KEY` nicht verifiziert** – an echten Pilotdokumenten validieren |
|
||
| 5 | Backoffice prüft und korrigiert erkannte Daten | erfüllt | Prüfmaske `/imports/[id]` (unsichere Felder markiert, Korrektur-Diff, keine Auftragsanlage ohne Bestätigung); `test-import-flow` K-Fälle | |
|
||
| 6 | Aufträge Teams zuweisbar | erfüllt | L2 `assignWorkOrder` + Event `work_order.assigned` → L6 Empfänger; `test-auftraege-core`, `test-benachrichtigungen-events` | |
|
||
| 7 | Monteure bearbeiten Aufträge auf Tablet/Smartphone | erfüllt | L4 `/m/**` (eigene Shell, Bottom-Nav, Touch ≥ 48 px); `test-einsatz-field`; authentifizierter Smoke `scripts/smoke-auth.ts` | `smoke-auth.ts` 94/94 Seiten-Prüfungen für alle Rollen + zweiten Mandanten; visuelle Prüfung 375/768/1024 px im Browser offen |
|
||
| 8 | Wesentliche Daten offline erfassbar | erfüllt | L7 Service Worker, IndexedDB-Outbox, `/m/offline`, `/m/sync`; `test-offline-core`, `test-offline-sync-e2e` (20 Ops → 1 Batch, Replay → duplicate) | Manueller DevTools-Offline-Test; Abschließen offline bewusst gesperrt (Pflichtprüfung server-seitig) |
|
||
| 9 | Arbeitszeiten, Tätigkeiten, Material, Fotos dokumentierbar | erfüllt | L4 Sessions/TimeEntry, Notizen, Material (Abweichungsgrund Pflicht), Fotos (Kompression, Pflichtfotos); `test-einsatz-field`, `test-einsatz-sync` | |
|
||
| 10 | Tagesberichte erzeugbar | erfüllt | L5 Tagesbericht (Content-Snapshot je Tag, Auftrag bleibt offen); `test-berichte-flow` | |
|
||
| 11 | Abschlussberichte inkl. Kundenunterschrift | erfüllt | L5 Abschlussbericht, Unterschrift inkl. Abwesend/Verweigert/Später mit Begründung; `test-berichte-flow` | |
|
||
| 12 | PDF-Arbeitsnachweis erstellt | erfüllt | L5 HTML→PDF (Playwright/Chromium im Worker), Checksumme, unveränderliche Version; `test-berichte-pdf` | Worker-Image `craftvia-worker` mit Chromium lokal gebaut und PDF erzeugt (L10b); Registry-Push/Prod-Deploy offen |
|
||
| 13 | Backoffice gibt abgeschlossene Aufträge zur Abrechnung frei | erfüllt | L2 `releaseForBilling` nur mit freigegebenem Abschlussbericht; `test-auftraege-status` | |
|
||
| 14 | Monteure legen selbstständig Notdiensteinsätze an | erfüllt | L8 `/m/emergency` (vorläufiger Kunde/Objekt in einer Transaktion, offline als Sync-Op); `test-notdienst-flow` | |
|
||
| 15 | Backoffice wird über Notdiensteinsätze informiert | erfüllt | L6 Pflichtmail + In-App bei `emergency.created/completed` (Format §19.4); `test-notdienst-flow`, `test-benachrichtigungen-events` | Echter SMTP-Versand in Prod konfigurieren |
|
||
| 16 | Frühere Einsätze und technische Dokumente pro Objekt abrufbar | erfüllt | L1 Objekt-Historie + Dokumente (Sichtbarkeit/Scope), L4 mobile Historie; `test-stammdaten-sites`, `test-stammdaten-documents` | |
|
||
| 17 | Relevante Änderungen revisionsfähig protokolliert | erfüllt | `writeAuditLog` (before/after, IP, User-Agent) in allen Mutationen, Audit-Viewer `/settings/audit`; `test-audit-mail-context`, `test-benachrichtigungen-inbox-audit` | Audit-Einträge in Transaktionen erst nach Commit (L10b); Tamper-Schutz/Log-Aggregation (Betrieb) offen |
|
||
|
||
## 2. User Stories (Spec §37)
|
||
|
||
| US | Titel | Status | Nachweis | Offene Punkte |
|
||
|---|---|---|---|---|
|
||
| US-001 | Mandant anlegen | erfüllt | Plattform-Konsole `/admin` (Fundament), `provisionTenant`; Isolationstests | |
|
||
| US-002 | Auftragsbestätigung importieren | teilweise | L3 (s. §40 #3–5); `test-import-flow`, `test-e2e-*` (Import → Abrechnung) | Live-Texterkennung/Extraktion mit Claude nicht verifiziert (kein API-Schlüssel) |
|
||
| US-003 | Bestehenden Kunden erkennen | erfüllt | L1 `findDuplicateCustomers` (keine Auto-Zusammenführung), L3 Kandidaten in Prüfmaske; `test-stammdaten-duplicates` | |
|
||
| US-004 | Auftrag einem Team zuweisen | erfüllt | L2 + L6 | |
|
||
| US-005 | Technische Unterlagen ansehen | erfüllt | L1/L4 Dokumente mit Sichtbarkeit (backoffice_only verborgen), freigegebene Historie; L7 Vorab-Download offline | |
|
||
| US-006 | Einsatz dokumentieren | erfüllt | L4 | |
|
||
| US-007 | Tagesbericht erstellen | erfüllt | L5 | |
|
||
| US-008 | Abschlussbericht erstellen | erfüllt | L5 (Pflichtangaben/-fotos geprüft, Status „Zur Prüfung") | |
|
||
| US-009 | Auftrag zur Abrechnung freigeben | erfüllt | L2 + L5 + L6 | |
|
||
| US-010 | Notdiensteinsatz anlegen | erfüllt | L8 + L6 | |
|
||
| US-011 | Objekt-Historie aufrufen | erfüllt | L1 `getSiteHistory` (offene Folgearbeiten hervorgehoben) | |
|
||
| US-012 | Offline arbeiten | erfüllt | L7 + L4 Sync-Server (Idempotenz, Konflikte nicht still überschrieben) | |
|
||
|
||
## 3. Soll-/Kann-Funktionen (Spec §36.2/§36.3)
|
||
|
||
| Funktion | Status | Nachweis / Anmerkung |
|
||
|---|---|---|
|
||
| Sprachnotizen + Transkription | erfüllt | L4 Aufnahme, L9 Whisper-kompatibler Provider (`test-lotse-transcription`); ohne `TRANSCRIPTION_API_KEY` Status `disabled` |
|
||
| KI-Berichtsentwurf (Lotse) | erfüllt | L9 Vorschlag in `content.lotse`, Prüfbestätigung serverseitig Pflicht, Datenminimierung (`test-lotse-draft`) |
|
||
| Konfigurierbare Vorlagen | erfüllt | L2 `/settings/order-types`, `/settings/checklists` |
|
||
| E-Mail-Versand von Berichten | erfüllt | L6 interne Benachrichtigungen; L11 „An Kunden senden“ auf `/reports/[id]` und `POST /api/v1/reports/{id}/send`: freigegebenes PDF als Anhang (Queue enthält nur Document-Referenz; Zustellung lädt Bytes mandantengebunden mit Prüfsummenprüfung), Recht `report:approve`, Audit, Dedupe je Adresse+Version; `test-report-customer-mail` (41 Prüfungen), realer Versand an Mailhog geprüft. Offen: kein erneutes Senden an dieselbe Adresse, ein Empfänger ohne CC, Sprache = Mandant |
|
||
| Berichtsversionierung | erfüllt | L5 `lineageId`/`version`, alte Version erst bei Freigabe der neuen `superseded` |
|
||
| MFA | erfüllt | Fundament TOTP + Passkeys, Mandanten-MFA-Pflicht |
|
||
| Kartenlink zur Einsatzadresse | erfüllt | L1 OpenStreetMap-Link, L4 Routenlink |
|
||
| Push-Benachrichtigungen | offen | nach MVP (Spec §20.2 „später") |
|
||
| Kalenderansicht | offen | optional (§11.2) |
|
||
| Standorterfassung beim Einsatzstart | erfüllt | L4 optional beim Session-Start |
|
||
| Barcode/QR-Code | offen | Version 2 |
|
||
| Kundenspezifische Statusmodelle | offen | Statusmodell global, Labels gruppiert (Brandbook §12.3) |
|
||
| Digitale Einsatzfreigabe durch Teamleiter | erfüllt | L5 `report:approve_team` |
|
||
|
||
## 4. Offene fachliche Entscheidungen (Spec §44) – getroffene MVP-Annahmen
|
||
|
||
| Thema | MVP-Annahme | Zu klären mit Pilotkunde |
|
||
|---|---|---|
|
||
| Berichtsvorlage | generische Vorlage L5 mit Mandantenlogo/-namen | Layout/Pflichtinhalte des Pilotkunden |
|
||
| Pflichtfelder je Auftragsart | über Checklisten-Vorlagen je Auftragsart konfigurierbar | konkrete Pflichtfelder |
|
||
| Fotoarten | Standard-Pflichtfotos §14.2 als Vorschläge | verbindliche Liste |
|
||
| Arbeitszeitkorrektur | nur mit `field:correct_time` (Teamleiter/Admin), Grund Pflicht, Audit | Genehmigungsprozess? |
|
||
| Aufbewahrungsfristen | Soft Delete, keine automatische Löschung; KI-Protokoll: `AI_GENERATION_RETENTION_DAYS` (Default 180): täglicher Worker-Job leert Ein-/Ausgaben und Personenbezug im KI-Protokoll; monatliches Token-Kontingent je Mandant (`/settings/lotse`) | Fristen je Dokumentart |
|
||
| Empfänger Abrechnungsbenachrichtigung | `/settings/email` Abrechnungsempfänger je Mandant | Adressen |
|
||
| OCR-/KI-Anbieter | Claude (Anthropic) für Extraktion + Lotse, Whisper-kompatibel für Transkription (Entscheidung 2026-09-14) | Vertrag/AVV, Region |
|
||
| E-Mail-Dienst | SMTP (Mail-Worker, Queue) | Anbieter, SPF/DKIM/DMARC |
|
||
| Hostingstandort | Coolify/Docker (aus Certvia übernommen) | Rechenzentrum/Region |
|
||
| MFA-Regelung | optional je Nutzer, Mandanten-Pflicht einschaltbar | Pflicht für Admins? |
|
||
| Standortdaten | optional beim Einsatzstart/Foto, kein Dauertracking | Einwilligung/Betriebsvereinbarung |
|
||
| Maximale Offline-Dauer | `OFFLINE_MAX_DAYS=7` | Wert |
|
||
| Maximale Dateigrößen | Bild 15 MB, PDF 25 MB, Audio 20 MB | Werte |
|
||
| Buchhaltungssoftware | keine Schnittstelle (MVP-Abgrenzung §35) | Zielsystem für V2 |
|
||
| Spätere Schnittstellen | versionierte API `/api/v1` + OpenAPI | Prioritäten |
|
||
| Branding der PWA | Craftvia (Brandbook), Mandantenlogo in Berichten | Mandanten-Branding in App? |
|
||
| Berichtssprache | Deutsch (EN-Kataloge vorbereitet) | |
|
||
| Lösch-/Archivierungskonzept | DSGVO-Export/-Löschung aus Fundament, PII-Liste um Fachfelder erweitert | Löschfristen |
|
||
|
||
## 5. Bekannte Einschränkungen vor Produktivbetrieb
|
||
|
||
- **Login-Drosselung:** Konto-Lockout nach 5 Fehlversuchen vorhanden; zusätzlich Drosselung je IP und je Konto (Scope `login`, 20 Versuche / 15 min, `LOGIN_RATE_LIMIT_PER_15_MIN`; `test-login-rate-limit`). Zähler im Speicher je App-Instanz.
|
||
- **Polyglot-Uploads:** gültiger Bildkopf mit angehängtem Inhalt passiert die Typprüfung (Auslieferung als Download mit erkanntem Typ + `nosniff`, keine Ausführung); echte Inhaltsprüfung nur mit ClamAV (`CLAMAV_HOST`).
|
||
- **Rate-Limit** `/api/v1` je App-Instanz im Speicher (bei mehreren Instanzen Redis-Zähler nötig); API nur mit Session-Cookie (kein Token-Zugang für Integrationen).
|
||
- **Mobile Listen** ohne Paginierung (max. 200 bzw. 100 heute) – für MVP-Größen ausreichend (Performance-Messung 5 020 Aufträge: Dashboard/Liste 30–53 ms, mobil ~110 ms im Produktions-Build).
|
||
- **`emitEvent` in Transaktionen** feuert sofort (bei späterem Rollback evtl. Benachrichtigung ohne Datenänderung).
|
||
- **Unterschrift offline:** Sync-Op `signature.capture` nicht registriert (Unterschrift erfordert Verbindung).
|
||
- **Live-KI** (Claude-Extraktion, Lotse, Transkription) ohne API-Schlüssel nicht verifiziert.
|
||
- **Deployment:** Images lokal gebaut (App 400 MB, Worker 2,68 GB inkl. Chromium), kein Registry-Push, CI-Testjob nicht auf Server ausgeführt.
|
||
- **Demo-Seed:** Daten relativ zum Seed-Zeitpunkt.
|
||
|
||
## 6. Nicht verifiziert in dieser Umgebung
|
||
|
||
- Live-Aufrufe gegen Claude (Extraktion, Lotse) und Transkription – keine API-Schlüssel gesetzt.
|
||
- Visuelle Prüfung mit echter Passwort-Anmeldung im Browser (Agenten geben keine Passwörter ein; angemeldete Prüfungen per `scripts/smoke-auth.ts`).
|
||
- Manueller Offline-Test mit Browser-DevTools.
|
||
- Deployment auf Zielumgebung (nur lokaler Docker-Build).
|