Iteration 4 (Maßnahmen) + Risikoanalyse nachgeschärft

Risikoanalyse:
- Risiko direkt aus dem Asset-Popup erstellbar ("+ Risiko erstellen"
  → Anlege-Popup mit vorverknüpftem Asset)
- Brutto-/Rest-Risiko detailliert: Eintrittswahrscheinlichkeit und
  Schaden als beschriftete Werte (X × Y = Score-Pille)
- Rest-Risiko wird NICHT mehr manuell erfasst, sondern automatisch aus
  den verknüpften Maßnahmen berechnet (Minderung je Dimension, Summe,
  Untergrenze 1; src/server/risk-calc.ts); Neuberechnung bei jeder
  Änderung an Bewertung oder Maßnahmen-Verknüpfungen
- Risikoregister unterhalb der Heatmap in voller Breite

Maßnahmen-Modul (SPEC §4.4):
- Measure/RiskMeasure-Schema mit RLS, laufende Nummer (M-001),
  Status/Priorität/Owner/Fälligkeit
- Kanban-Board mit Drag-and-Drop (dnd-kit): Karten zwischen Offen /
  In Umsetzung / Erledigt verschieben aktualisiert den Status per
  Server-Action inkl. Audit-Log; überfällige Karten rot markiert
- Maßnahmen-Popups (Detail read-only / Bearbeiten / Anlegen) nach
  App-Muster; Detail zeigt verknüpfte Risiken mit Minderung
- Im Risiko-Bearbeiten: bestehende Maßnahme verknüpfen ODER neue
  Maßnahme direkt anlegen & verknüpfen (jeweils mit Minderung
  Wahrscheinlichkeit/Schaden); "Notwendige Maßnahmen" im Risiko-Detail
  jetzt echt
- Seed: 4 Beispiel-Maßnahmen, Rest-Risiken daraus berechnet

Verifiziert im Browser: DnD-Statuswechsel (beide Richtungen, Audit-
Einträge), Rest-Risiko-Berechnung (R-001: 4×5=20 → 2×4=8), Risiko-
Anlage aus dem Asset inkl. automatischer Verknüpfung.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Martin
2026-07-02 16:07:59 +02:00
co-authored by Claude Fable 5
parent df634e356d
commit 668e587881
20 changed files with 1534 additions and 102 deletions
+65 -3
View File
@@ -61,6 +61,7 @@ model User {
ownedAssets Asset[] @relation("assetOwner")
ownedProcesses Process[] @relation("processOwner")
ownedRisks Risk[] @relation("riskOwner")
ownedMeasures Measure[] @relation("measureOwner")
@@unique([tenantId, email])
@@index([tenantId])
@@ -292,9 +293,10 @@ model Risk {
updatedAt DateTime @updatedAt @map("updated_at")
createdBy String? @map("created_by")
owner User? @relation("riskOwner", fields: [ownerId], references: [id])
process Process? @relation(fields: [processId], references: [id])
riskAssets RiskAsset[]
owner User? @relation("riskOwner", fields: [ownerId], references: [id])
process Process? @relation(fields: [processId], references: [id])
riskAssets RiskAsset[]
riskMeasures RiskMeasure[]
@@unique([tenantId, refNo])
@@index([tenantId])
@@ -316,6 +318,66 @@ model RiskAsset {
@@map("risk_assets")
}
// ── Maßnahmen (SPEC §4.4) ───────────────────────────────────────────────────
enum MeasureStatus {
OPEN
IN_PROGRESS
DONE
}
enum MeasurePriority {
LOW
MEDIUM
HIGH
}
model Measure {
id String @id @default(cuid())
tenantId String @map("tenant_id")
refNo Int @map("ref_no") // Anzeige als "M-001"
title String
description String?
status MeasureStatus @default(OPEN)
priority MeasurePriority @default(MEDIUM)
ownerId String? @map("owner_id")
dueDate DateTime? @map("due_date")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
createdBy String? @map("created_by")
owner User? @relation("measureOwner", fields: [ownerId], references: [id])
riskMeasures RiskMeasure[]
@@unique([tenantId, refNo])
@@index([tenantId])
@@index([tenantId, status])
@@map("measures")
}
// Verknüpfung Risiko ↔ Maßnahme inkl. erwarteter Risikominderung:
// Das Rest-Risiko wird aus likelihood/impact minus der Summe der
// Minderungen aller verknüpften Maßnahmen berechnet (siehe src/server/risk-calc.ts).
model RiskMeasure {
id String @id @default(cuid())
tenantId String @map("tenant_id")
riskId String @map("risk_id")
measureId String @map("measure_id")
// Minderung in Stufen (04) je Dimension
reductionLikelihood Int @default(0) @map("reduction_likelihood")
reductionImpact Int @default(0) @map("reduction_impact")
risk Risk @relation(fields: [riskId], references: [id], onDelete: Cascade)
measure Measure @relation(fields: [measureId], references: [id], onDelete: Cascade)
@@unique([riskId, measureId])
@@index([tenantId])
@@map("risk_measures")
}
model AuditLog {
id String @id @default(cuid())
tenantId String @map("tenant_id")