/** * Anforderungs-Engine für Lieferanten (VDA-ISA 2027 Kap. 6, NIS2 Art. 21(2)(d)). * Schutzbedarf → Anforderungsstufe; flacher Anforderungskatalog mit * Status-Ableitung aus den hinterlegten Objekten; Gate-Logik & Konformität. */ export function supplierRef(refNo: number): string { return `L-${String(refNo).padStart(3, "0")}`; } export function serviceRef(refNo: number): string { return `S-${String(refNo).padStart(3, "0")}`; } /** * Hängt einen Query-Parameter an einen Basis-Pfad an und wählt automatisch * das richtige Trennzeichen (`?` oder `&`). Erlaubt, Detail-/Bearbeiten-Popups * sowohl auf /suppliers als auch auf /assets zu öffnen (backHref-Muster). */ export function withQuery(base: string, key: string, val: string): string { return `${base}${base.includes("?") ? "&" : "?"}${key}=${val}`; } export const SUPPLIER_LIFECYCLE_TONE = { ACTIVE: "ok", ONBOARDING: "info", UNDER_REVIEW: "warn", OFFBOARDED: "mut", } as const; const DAY = 24 * 3600 * 1000; export function isExpired(d: Date | null | undefined): boolean { return !!d && d.getTime() < Date.now(); } export function isExpiring(d: Date | null | undefined, days = 90): boolean { return !!d && d.getTime() - Date.now() < days * DAY; } export function isReviewDue(d: Date | null | undefined, days = 30): boolean { return !!d && d.getTime() < Date.now() + days * DAY; } /** Schutzbedarf 1(normal)/2(hoch)/3(sehr hoch) aus der höchsten C/I/A-Stufe. */ export type ProtectionLevel = 1 | 2 | 3; export function protectionLevel(maxCia: number): ProtectionLevel { if (maxCia >= 4) return 3; if (maxCia >= 3) return 2; return 1; } export const PROTECTION_LABEL: Record = { 1: "normal", 2: "hoch", 3: "sehr hoch", }; /** Kontext für die Statusableitung — aus dem Lieferanten-Datensatz berechnet. */ export type ReqContext = { hasValidNda: boolean; ndaMonitored: boolean; hasSecurityContract: boolean; customerReqPassed: boolean; hasFlowdown: boolean; hasAssessment: boolean; hasReviewedAssessment: boolean; hasAdequateEvidence: boolean; complianceMonitored: boolean; hasLinkedRisk: boolean; hasManagementDecision: boolean; hasAudit: boolean; }; export type Tier = "must" | "should" | "high" | "veryhigh"; export type ReqStatus = "met" | "partial" | "open" | "na"; export type ReqTheme = "contract" | "assessment" | "transparency"; export type Requirement = { key: string; label: string; control: "6.1.1" | "6.1.2" | "6.1.3"; tier: Tier; theme: ReqTheme; met: (c: ReqContext) => boolean; refLabel?: (c: ReqContext) => string | null; }; export const REQUIREMENTS: Requirement[] = [ // Vertrag & Geheimhaltung { key: "nda_valid", label: "NDA abgeschlossen und gültig", control: "6.1.2", tier: "must", theme: "contract", met: (c) => c.hasValidNda }, { key: "infosec_contract", label: "Vertragliche InfoSec-Vereinbarung geschlossen", control: "6.1.1", tier: "must", theme: "contract", met: (c) => c.hasSecurityContract }, { key: "customer_req_passed", label: "Kundenanforderungen an Partner weitergegeben", control: "6.1.1", tier: "must", theme: "contract", met: (c) => c.customerReqPassed }, { key: "flowdown", label: "Flow-down an Subunternehmer verpflichtet", control: "6.1.1", tier: "should", theme: "contract", met: (c) => c.hasFlowdown }, { key: "nda_monitoring", label: "NDA-Fristen-Monitoring aktiv", control: "6.1.2", tier: "should", theme: "contract", met: (c) => c.ndaMonitored }, // Bewertung & Nachweise { key: "risk_assessment", label: "Sicherheits-Risikobewertung durchgeführt", control: "6.1.1", tier: "must", theme: "assessment", met: (c) => c.hasAssessment || c.hasLinkedRisk }, { key: "reports_reviewed", label: "Service-Reports & Dokumente geprüft", control: "6.1.1", tier: "should", theme: "assessment", met: (c) => c.hasReviewedAssessment }, { key: "adequate_evidence", label: "Nachweis angemessenes Sicherheitsniveau (Zertifikat/Audit)", control: "6.1.1", tier: "high", theme: "assessment", met: (c) => c.hasAdequateEvidence }, { key: "compliance_monitored", label: "Compliance-Grad dokumentiert & anlassbezogen überwacht", control: "6.1.1", tier: "high", theme: "assessment", met: (c) => c.complianceMonitored }, // Transparenz / sehr hoch { key: "customer_transparency", label: "Kundentransparenz zu Lieferkettenrisiken erfüllt", control: "6.1.1", tier: "veryhigh", theme: "transparency", met: (c) => c.customerReqPassed }, ]; const TIER_MIN_LEVEL: Record = { must: 1, should: 1, high: 2, veryhigh: 3 }; export function tierActive(tier: Tier, level: ProtectionLevel): boolean { return level >= TIER_MIN_LEVEL[tier]; } export function requirementStatus(req: Requirement, ctx: ReqContext, level: ProtectionLevel): ReqStatus { if (!tierActive(req.tier, level)) return "na"; return req.met(ctx) ? "met" : "open"; } /** Gate: sehr hoher Schutzbedarf ohne gültiges Audit/Label → Kompensation nötig. */ export function gateActive(level: ProtectionLevel, ctx: ReqContext): boolean { return level === 3 && !ctx.hasAudit; } export function gateSatisfied(ctx: ReqContext): boolean { return ctx.hasAudit || (ctx.hasManagementDecision && ctx.hasLinkedRisk); } export type Conformity = "conform" | "partial" | "gaps" | "action"; export function conformity(level: ProtectionLevel, ctx: ReqContext): Conformity { if (gateActive(level, ctx) && !gateSatisfied(ctx)) return "action"; const active = REQUIREMENTS.filter((r) => tierActive(r.tier, level)); const met = active.filter((r) => r.met(ctx)).length; if (met === active.length) return "conform"; if (met >= Math.ceil(active.length * 0.6)) return "partial"; return "gaps"; } export const CONFORMITY_TONE: Record = { conform: "ok", partial: "warn", gaps: "risk", action: "risk", }; export const CONFORMITY_LABEL: Record = { conform: "Konform", partial: "Teilweise", gaps: "Lücken offen", action: "Handlung nötig", }; /** Berechneter Reifegrad (0–3) aus Erfüllungsgrad der aktiven Anforderungen. */ export function computedMaturity(level: ProtectionLevel, ctx: ReqContext): number { const active = REQUIREMENTS.filter((r) => tierActive(r.tier, level)); if (active.length === 0) return 0; const met = active.filter((r) => r.met(ctx)).length; return Math.round((met / active.length) * 3 * 10) / 10; } export const TARGET_MATURITY = 3.0; type CtxInput = { contracts: { securityClauses: boolean; flowdown: boolean; customerRequirementsPassed: boolean }[]; ndas: { validTo: Date | null }[]; evidence: { kind: string; adequacyChecked: boolean; validTo: Date | null }[]; assessments: { result: string | null; nextReview: Date | null }[]; subcontractors: { flowdownObligation: boolean }[]; nextReview: Date | null; linkedRiskCount: number; decisionCount: number; }; /** Baut den ReqContext aus den hinterlegten Lieferanten-Objekten. */ export function buildReqContext(d: CtxInput): ReqContext { const hasAudit = d.evidence.some( (e) => (e.kind === "AUDIT_REPORT" || e.kind === "TISAX_LABEL") && e.adequacyChecked && !isExpired(e.validTo) ); const hasAdequateEvidence = d.evidence.some((e) => e.adequacyChecked && !isExpired(e.validTo)); return { hasValidNda: d.ndas.some((n) => !isExpired(n.validTo)), ndaMonitored: d.ndas.length > 0 && !!d.nextReview, hasSecurityContract: d.contracts.some((c) => c.securityClauses), customerReqPassed: d.contracts.some((c) => c.customerRequirementsPassed), hasFlowdown: d.contracts.some((c) => c.flowdown) || d.subcontractors.some((s) => s.flowdownObligation), hasAssessment: d.assessments.length > 0, hasReviewedAssessment: d.assessments.some((a) => !!a.result), hasAdequateEvidence, complianceMonitored: d.assessments.some((a) => !!a.nextReview), hasLinkedRisk: d.linkedRiskCount > 0, hasManagementDecision: d.decisionCount > 0, hasAudit, }; }