// AP4 — Managementklauseln (9.1 Kennzahlen, 9.3 Managementbewertung, 10.2 CAPA). // Prüft Datenmodell, Relationen und Kern-Invarianten der DoD gegen die lokale DB // (Wegwerf-Mandant, dbForTenant → RLS-Pfad). // // Lauf: npx tsx scripts/test-review.ts import "dotenv/config"; import { prisma, dbForTenant } from "../src/server/db"; let failures = 0; const ok = (c: boolean, m: string) => { console.log(`${c ? "✓" : "✗ FEHLER"} ${m}`); if (!c) failures++; }; const SLUG = "ap4-test-review"; async function cleanup() { const t = await prisma.tenant.findUnique({ where: { slug: SLUG }, select: { id: true } }); if (!t) return; const id = t.id; await prisma.correctiveAction.deleteMany({ where: { tenantId: id } }); await prisma.nonconformity.deleteMany({ where: { tenantId: id } }); await prisma.managementReviewDecision.deleteMany({ where: { tenantId: id } }); await prisma.managementReview.deleteMany({ where: { tenantId: id } }); await prisma.kpiValue.deleteMany({ where: { tenantId: id } }); await prisma.kpi.deleteMany({ where: { tenantId: id } }); await prisma.tenant.delete({ where: { id } }); } async function main() { await cleanup(); const tenant = await prisma.tenant.create({ data: { name: "AP4", slug: SLUG } }); const t = tenant.id; const db = dbForTenant(t); // ── 9.1 Kennzahl über zwei Perioden auswertbar ────────────────────────────── const kpi = await db.kpi.create({ data: { tenantId: t, name: "Überfällige Maßnahmen", target: "< 5%", unit: "%", cadence: "quartalsweise" }, select: { id: true } }); for (const [period, value] of [["2026-Q1", "8%"], ["2026-Q2", "4%"]]) { await db.kpiValue.upsert({ where: { tenantId_kpiId_period: { tenantId: t, kpiId: kpi.id, period } }, update: { value }, create: { tenantId: t, kpiId: kpi.id, period, value }, }); } // Upsert derselben Periode → Update statt Duplikat (Unique). await db.kpiValue.upsert({ where: { tenantId_kpiId_period: { tenantId: t, kpiId: kpi.id, period: "2026-Q2" } }, update: { value: "3%" }, create: { tenantId: t, kpiId: kpi.id, period: "2026-Q2", value: "3%" } }); const vals = await db.kpiValue.findMany({ where: { kpiId: kpi.id }, orderBy: { period: "asc" } }); ok(vals.length === 2, `Kennzahl über zwei Perioden auswertbar (${vals.length} Werte, keine Dubletten)`); ok(vals[1].value === "3%", "Messwert je Periode ist aktualisierbar (Upsert)"); // ── 9.3 Managementbewertung entlang 9.3.2 mit Beschlüssen ─────────────────── const review = await db.managementReview.create({ data: { tenantId: t, reviewDate: new Date("2026-06-01"), inputs: "Kennzahlen, Auditergebnisse, Vorjahresmaßnahmen …", results: "Ressourcen genehmigt" }, select: { id: true } }); await db.managementReviewDecision.create({ data: { tenantId: t, reviewId: review.id, decision: "Zusätzliche Awareness-Schulung", dueDate: new Date("2026-09-30") } }); const d2 = await db.managementReviewDecision.create({ data: { tenantId: t, reviewId: review.id, decision: "Backup-Konzept prüfen" }, select: { id: true } }); await db.managementReviewDecision.update({ where: { id: d2.id }, data: { status: "erledigt" } }); const withDec = await db.managementReview.findUnique({ where: { id: review.id }, include: { decisions: true } }); ok(withDec?.decisions.length === 2, "Managementbewertung mit Beschlüssen (Verantwortlicher/Termin) protokollierbar"); ok(withDec?.decisions.filter((d) => d.status === "erledigt").length === 1, "Beschluss-Status nachverfolgbar (1 erledigt)"); await db.managementReview.update({ where: { id: review.id }, data: { status: "abgeschlossen" } }); ok((await db.managementReview.findUnique({ where: { id: review.id } }))?.status === "abgeschlossen", "Managementbewertung abschließbar"); // ── 10.2 Nichtkonformität + Korrekturmaßnahme inkl. Wirksamkeit ────────────── const nc = await db.nonconformity.create({ data: { tenantId: t, refNo: "NC-2026-0001", source: "Internes Audit", description: "Zugriffsrechte nicht rezertifiziert", immediateCorrection: "Sofort-Review" }, select: { id: true } }); const nc2 = await db.nonconformity.create({ data: { tenantId: t, refNo: "NC-2026-0002", source: "Vorfall", description: "Test" }, select: { refNo: true } }); ok(nc2.refNo === "NC-2026-0002", "fortlaufende NC-Kennung (NC-2026-0002)"); const ca = await db.correctiveAction.create({ data: { tenantId: t, nonconformityId: nc.id, action: "Rezertifizierungs-Prozess etablieren", rootCause: "Kein definierter Turnus" }, select: { id: true } }); // Wirksamkeit dokumentieren + Maßnahme umgesetzt. await db.correctiveAction.update({ where: { id: ca.id }, data: { status: "umgesetzt", effectivenessCheck: "Rezertifizierung im Folgequartal vollständig", effectivenessConfirmedAt: new Date() } }); const caAfter = await db.correctiveAction.findUnique({ where: { id: ca.id } }); ok(!!caAfter?.effectivenessConfirmedAt && !!caAfter?.effectivenessCheck, "Korrekturmaßnahme mit dokumentierter Wirksamkeitsprüfung"); // Nichtkonformität schließen. await db.nonconformity.update({ where: { id: nc.id }, data: { status: "abgeschlossen", closedAt: new Date() } }); const ncAfter = await db.nonconformity.findUnique({ where: { id: nc.id } }); ok(ncAfter?.status === "abgeschlossen" && !!ncAfter?.closedAt, "Maßnahmenfall inkl. Wirksamkeitsprüfung abschließbar"); await cleanup(); console.log("\n✓ aufgeräumt (Wegwerf-Mandant entfernt)"); } main() .then(() => { console.log(failures === 0 ? "\nAP4-Managementklauseln grün." : `\n${failures} Prüfung(en) fehlgeschlagen.`); process.exit(failures === 0 ? 0 : 1); }) .catch(async (e) => { console.error(e); await cleanup().catch(() => {}); process.exit(1); });