Jeder Tenant kann jetzt mehrere Compliance-Projekte anlegen (z.B. verschiedene Produkte, Tochterunternehmen). CompanyProfile ist pro Projekt kopierbar und danach unabhaengig editierbar. Multi-Tab-Support via separater BroadcastChannel und localStorage Keys pro Projekt. - Migration 039: compliance_projects Tabelle, sdk_states.project_id - Backend: FastAPI CRUD-Routes fuer Projekte mit Tenant-Isolation - Frontend: ProjectSelector UI, SDKProvider mit projectId, URL ?project= - State API: UPSERT auf (tenant_id, project_id) mit Abwaertskompatibilitaet - Tests: pytest fuer Model-Validierung, Row-Konvertierung, Tenant-Isolation - Docs: MKDocs Seite, CLAUDE.md, Backend README Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
5.7 KiB
Multi-Projekt-Architektur
Jeder Tenant kann mehrere Compliance-Projekte anlegen (z.B. verschiedene Produkte, Tochterunternehmen). CompanyProfile ist pro Projekt — nicht tenant-weit.
Uebersicht
graph TD
T[Tenant] --> P1[Projekt A: KI-Produkt X]
T --> P2[Projekt B: SaaS API]
T --> P3[Projekt C: Tochter GmbH]
P1 --> S1[SDK State A]
P2 --> S2[SDK State B]
P3 --> S3[SDK State C]
S1 --> CP1[CompanyProfile A]
S2 --> CP2[CompanyProfile B]
S3 --> CP3[CompanyProfile C]
Datenmodell
compliance_projects
| Spalte | Typ | Beschreibung |
|---|---|---|
id |
UUID | Primaerschluessel |
tenant_id |
VARCHAR(255) | Tenant-Zuordnung |
name |
VARCHAR(500) | Projektname |
description |
TEXT | Beschreibung |
customer_type |
VARCHAR(20) | 'new' oder 'existing' |
status |
VARCHAR(20) | 'active', 'archived', 'deleted' |
project_version |
INTEGER | Versionszaehler |
completion_percentage |
INTEGER | Fortschritt (0-100) |
created_at |
TIMESTAMPTZ | Erstellungszeitpunkt |
updated_at |
TIMESTAMPTZ | Letzte Aenderung |
archived_at |
TIMESTAMPTZ | Archivierungszeitpunkt |
sdk_states (erweitert)
- UNIQUE-Constraint auf
(tenant_id, project_id)statt nurtenant_id project_id UUID NOT NULL— FK aufcompliance_projects(id) ON DELETE CASCADE
Daten-Ownership
| Daten | Scope | Speicherort |
|---|---|---|
| Firmenname, Rechtsform, DSB, Standorte | Pro Projekt | sdk_states.state.companyProfile |
| Projektname, Typ, Status | Projekt | compliance_projects |
| SDK State (VVT, DSFA, TOM, etc.) | Projekt | sdk_states (JSONB) |
Backend API
Alle Endpoints sind tenant-isoliert via X-Tenant-ID Header.
Endpoints
| Method | Endpoint | Beschreibung |
|---|---|---|
| GET | /api/v1/projects |
Alle aktiven Projekte des Tenants |
| POST | /api/v1/projects |
Neues Projekt erstellen |
| GET | /api/v1/projects/{id} |
Einzelnes Projekt laden |
| PATCH | /api/v1/projects/{id} |
Projekt aktualisieren |
| DELETE | /api/v1/projects/{id} |
Projekt archivieren (Soft Delete) |
Projekt erstellen
POST /api/v1/projects
{
"name": "KI-Produkt X",
"description": "DSGVO-Compliance fuer Produkt X",
"customer_type": "existing",
"copy_from_project_id": "uuid-123"
}
Response (201):
{
"id": "uuid-new",
"tenant_id": "uuid-tenant",
"name": "KI-Produkt X",
"description": "DSGVO-Compliance fuer Produkt X",
"customer_type": "existing",
"status": "active",
"project_version": 1,
"completion_percentage": 0,
"created_at": "2026-03-09T12:00:00Z",
"updated_at": "2026-03-09T12:00:00Z"
}
Stammdaten-Kopie
Wenn copy_from_project_id angegeben, kopiert das Backend companyProfile aus dem Quell-State in den neuen State. Die kopierten Daten sind danach unabhaengig editierbar.
Anwendungsfall: Konzern mit Tochterfirmen — gleiche Rechtsform, aber unterschiedliche Adresse/Mitarbeiterzahl.
Projekt archivieren
DELETE /api/v1/projects/{id}
Setzt status='archived' und archived_at=NOW(). Archivierte Projekte erscheinen nicht in der Standardliste (nur mit ?include_archived=true).
Frontend
URL-Schema
/sdk → Projektliste (ProjectSelector)
/sdk?project={uuid} → Dashboard im Projekt-Kontext
/sdk/vvt?project={uuid} → VVT im Projekt-Kontext
/sdk/dsfa?project={uuid} → DSFA im Projekt-Kontext
Alle internen Links enthalten automatisch ?project=.
Komponenten
| Komponente | Datei | Beschreibung |
|---|---|---|
ProjectSelector |
components/sdk/ProjectSelector/ProjectSelector.tsx |
Projektliste + Erstellen-Dialog |
ProjectCard |
(gleiche Datei) | Einzelne Projektkarte |
CreateProjectDialog |
(gleiche Datei) | Modal fuer neues Projekt |
State-Isolation (Multi-Tab)
- BroadcastChannel:
sdk-state-sync-{tenantId}-{projectId}— pro Projekt - localStorage:
ai-compliance-sdk-state-{tenantId}-{projectId}— pro Projekt - Tab A (Projekt X) und Tab B (Projekt Y) interferieren nicht
SDKProvider
<SDKProvider
enableBackendSync={true}
projectId={searchParams.get('project')}
>
{children}
</SDKProvider>
Context-Methoden
const {
createProject, // (name, customerType) => Promise<ProjectInfo>
listProjects, // () => Promise<ProjectInfo[]>
switchProject, // (projectId) => void (navigiert zu ?project=)
archiveProject, // (projectId) => Promise<void>
projectId, // aktuelles Projekt-UUID
} = useSDK()
Migration (039)
Datei: backend-compliance/migrations/039_compliance_projects.sql
- Erstellt
compliance_projectsTabelle - Entfernt
UNIQUE(tenant_id)vonsdk_states - Fuegt
project_id UUIDzusdk_stateshinzu - Migriert bestehende Daten (Default-Projekt pro existierendem State)
- Setzt
project_idaufNOT NULL
Migration ausfuehren
ssh macmini "/usr/local/bin/docker exec bp-compliance-backend \
psql \$COMPLIANCE_DATABASE_URL -f /app/migrations/039_compliance_projects.sql"
Tests
# Backend-Tests
ssh macmini "/usr/local/bin/docker exec bp-compliance-backend \
pytest tests/test_project_routes.py -v"
| Test | Beschreibung |
|---|---|
TestCreateProjectRequest |
Model-Validierung (Name, Defaults) |
TestUpdateProjectRequest |
Partial Update Model |
TestRowToResponse |
DB-Row-zu-Response Konvertierung |
TestCreateProjectCopiesProfile |
Copy-Request Model |
TestTenantIsolation |
SQL-Queries filtern nach tenant_id |
TestStateIsolation |
sdk_states + companyProfile pro Projekt |