# Multi-Projekt-Architektur Jeder Tenant kann mehrere Compliance-Projekte anlegen (z.B. verschiedene Produkte, Tochterunternehmen). CompanyProfile ist **pro Projekt** — nicht tenant-weit. ## Uebersicht ```mermaid 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 nur `tenant_id` - `project_id UUID NOT NULL` — FK auf `compliance_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 ```json 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):** ```json { "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 ```typescript {children} ``` ### Context-Methoden ```typescript const { createProject, // (name, customerType) => Promise listProjects, // () => Promise switchProject, // (projectId) => void (navigiert zu ?project=) archiveProject, // (projectId) => Promise projectId, // aktuelles Projekt-UUID } = useSDK() ``` ## Migration (039) **Datei:** `backend-compliance/migrations/039_compliance_projects.sql` 1. Erstellt `compliance_projects` Tabelle 2. Entfernt `UNIQUE(tenant_id)` von `sdk_states` 3. Fuegt `project_id UUID` zu `sdk_states` hinzu 4. Migriert bestehende Daten (Default-Projekt pro existierendem State) 5. Setzt `project_id` auf `NOT NULL` ### Migration ausfuehren ```bash ssh macmini "/usr/local/bin/docker exec bp-compliance-backend \ psql \$COMPLIANCE_DATABASE_URL -f /app/migrations/039_compliance_projects.sql" ``` ## Tests ```bash # 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 |