feat(sdk): Multi-Projekt-Architektur — mehrere Projekte pro Tenant
Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Failing after 33s
CI / test-python-backend-compliance (push) Successful in 34s
CI / test-python-document-crawler (push) Successful in 23s
CI / test-python-dsms-gateway (push) Successful in 19s
Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Failing after 33s
CI / test-python-backend-compliance (push) Successful in 34s
CI / test-python-document-crawler (push) Successful in 23s
CI / test-python-dsms-gateway (push) Successful in 19s
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>
This commit is contained in:
190
docs-src/services/sdk-modules/multi-project.md
Normal file
190
docs-src/services/sdk-modules/multi-project.md
Normal file
@@ -0,0 +1,190 @@
|
||||
# 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
|
||||
<SDKProvider
|
||||
enableBackendSync={true}
|
||||
projectId={searchParams.get('project')}
|
||||
>
|
||||
{children}
|
||||
</SDKProvider>
|
||||
```
|
||||
|
||||
### Context-Methoden
|
||||
|
||||
```typescript
|
||||
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`
|
||||
|
||||
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 |
|
||||
Reference in New Issue
Block a user