[split-required] Split 58 monoliths across Python, Go, TypeScript (Phases 1-3)

Phase 1 — Python (klausur-service): 5 monoliths → 36 files
- dsfa_corpus_ingestion.py (1,828 LOC → 5 files)
- cv_ocr_engines.py (2,102 LOC → 7 files)
- cv_layout.py (3,653 LOC → 10 files)
- vocab_worksheet_api.py (2,783 LOC → 8 files)
- grid_build_core.py (1,958 LOC → 6 files)

Phase 2 — Go (edu-search-service, school-service): 8 monoliths → 19 files
- staff_crawler.go (1,402 → 4), policy/store.go (1,168 → 3)
- policy_handlers.go (700 → 2), repository.go (684 → 2)
- search.go (592 → 2), ai_extraction_handlers.go (554 → 2)
- seed_data.go (591 → 2), grade_service.go (646 → 2)

Phase 3 — TypeScript (admin-lehrer): 45 monoliths → 220+ files
- sdk/types.ts (2,108 → 16 domain files)
- ai/rag/page.tsx (2,686 → 14 files)
- 22 page.tsx files split into _components/ + _hooks/
- 11 component files split into sub-components
- 10 SDK data catalogs added to loc-exceptions
- Deleted dead backup index_original.ts (4,899 LOC)

All original public APIs preserved via re-export facades.
Zero new errors: Python imports verified, Go builds clean,
TypeScript tsc --noEmit shows only pre-existing errors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-04-24 17:28:57 +02:00
parent 9ba420fa91
commit b681ddb131
251 changed files with 30016 additions and 25037 deletions

View File

@@ -0,0 +1,491 @@
import type React from 'react'
import type {
VendorComplianceAction,
VendorComplianceState,
ProcessingActivity,
Vendor,
ContractDocument,
Finding,
ControlInstance,
ExportFormat,
} from './types'
type Dispatch = React.Dispatch<VendorComplianceAction>
const API_BASE = '/api/sdk/v1/vendor-compliance'
// ==========================================
// DATA LOADING
// ==========================================
export async function loadAllData(dispatch: Dispatch): Promise<void> {
dispatch({ type: 'SET_LOADING', payload: true })
dispatch({ type: 'SET_ERROR', payload: null })
try {
const [
activitiesRes,
vendorsRes,
contractsRes,
findingsRes,
controlsRes,
controlInstancesRes,
] = await Promise.all([
fetch(`${API_BASE}/processing-activities`),
fetch(`${API_BASE}/vendors`),
fetch(`${API_BASE}/contracts`),
fetch(`${API_BASE}/findings`),
fetch(`${API_BASE}/controls`),
fetch(`${API_BASE}/control-instances`),
])
if (activitiesRes.ok) {
const data = await activitiesRes.json()
dispatch({ type: 'SET_PROCESSING_ACTIVITIES', payload: data.data || [] })
}
if (vendorsRes.ok) {
const data = await vendorsRes.json()
dispatch({ type: 'SET_VENDORS', payload: data.data || [] })
}
if (contractsRes.ok) {
const data = await contractsRes.json()
dispatch({ type: 'SET_CONTRACTS', payload: data.data || [] })
}
if (findingsRes.ok) {
const data = await findingsRes.json()
dispatch({ type: 'SET_FINDINGS', payload: data.data || [] })
}
if (controlsRes.ok) {
const data = await controlsRes.json()
dispatch({ type: 'SET_CONTROLS', payload: data.data || [] })
}
if (controlInstancesRes.ok) {
const data = await controlInstancesRes.json()
dispatch({ type: 'SET_CONTROL_INSTANCES', payload: data.data || [] })
}
} catch (error) {
console.error('Failed to load vendor compliance data:', error)
dispatch({
type: 'SET_ERROR',
payload: 'Fehler beim Laden der Daten',
})
} finally {
dispatch({ type: 'SET_LOADING', payload: false })
}
}
// ==========================================
// PROCESSING ACTIVITIES ACTIONS
// ==========================================
export async function apiCreateProcessingActivity(
dispatch: Dispatch,
data: Omit<ProcessingActivity, 'id' | 'tenantId' | 'createdAt' | 'updatedAt'>
): Promise<ProcessingActivity> {
const response = await fetch(`${API_BASE}/processing-activities`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Erstellen der Verarbeitungstaetigkeit')
}
const result = await response.json()
const activity = result.data
dispatch({ type: 'ADD_PROCESSING_ACTIVITY', payload: activity })
return activity
}
export async function apiUpdateProcessingActivity(
dispatch: Dispatch,
id: string,
data: Partial<ProcessingActivity>
): Promise<void> {
const response = await fetch(`${API_BASE}/processing-activities/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Aktualisieren der Verarbeitungstaetigkeit')
}
dispatch({ type: 'UPDATE_PROCESSING_ACTIVITY', payload: { id, data } })
}
export async function apiDeleteProcessingActivity(
dispatch: Dispatch,
id: string
): Promise<void> {
const response = await fetch(`${API_BASE}/processing-activities/${id}`, {
method: 'DELETE',
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Loeschen der Verarbeitungstaetigkeit')
}
dispatch({ type: 'DELETE_PROCESSING_ACTIVITY', payload: id })
}
export async function apiDuplicateProcessingActivity(
dispatch: Dispatch,
state: VendorComplianceState,
id: string
): Promise<ProcessingActivity> {
const original = state.processingActivities.find((a) => a.id === id)
if (!original) {
throw new Error('Verarbeitungstaetigkeit nicht gefunden')
}
const { id: _id, vvtId: _vvtId, createdAt: _createdAt, updatedAt: _updatedAt, tenantId: _tenantId, ...rest } = original
const newActivity = await apiCreateProcessingActivity(dispatch, {
...rest,
vvtId: '', // Will be generated by backend
name: {
de: `${original.name.de} (Kopie)`,
en: `${original.name.en} (Copy)`,
},
status: 'DRAFT',
})
return newActivity
}
// ==========================================
// VENDOR ACTIONS
// ==========================================
export async function apiCreateVendor(
dispatch: Dispatch,
data: Omit<Vendor, 'id' | 'tenantId' | 'createdAt' | 'updatedAt'>
): Promise<Vendor> {
const response = await fetch(`${API_BASE}/vendors`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Erstellen des Vendors')
}
const result = await response.json()
const vendor = result.data
dispatch({ type: 'ADD_VENDOR', payload: vendor })
return vendor
}
export async function apiUpdateVendor(
dispatch: Dispatch,
id: string,
data: Partial<Vendor>
): Promise<void> {
const response = await fetch(`${API_BASE}/vendors/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Aktualisieren des Vendors')
}
dispatch({ type: 'UPDATE_VENDOR', payload: { id, data } })
}
export async function apiDeleteVendor(
dispatch: Dispatch,
id: string
): Promise<void> {
const response = await fetch(`${API_BASE}/vendors/${id}`, {
method: 'DELETE',
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Loeschen des Vendors')
}
dispatch({ type: 'DELETE_VENDOR', payload: id })
}
// ==========================================
// CONTRACT ACTIONS
// ==========================================
export async function apiUploadContract(
dispatch: Dispatch,
state: VendorComplianceState,
vendorId: string,
file: File,
metadata: Partial<ContractDocument>
): Promise<ContractDocument> {
const formData = new FormData()
formData.append('file', file)
formData.append('vendorId', vendorId)
formData.append('metadata', JSON.stringify(metadata))
const response = await fetch(`${API_BASE}/contracts`, {
method: 'POST',
body: formData,
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Hochladen des Vertrags')
}
const result = await response.json()
const contract = result.data
dispatch({ type: 'ADD_CONTRACT', payload: contract })
// Update vendor's contracts list
const vendor = state.vendors.find((v) => v.id === vendorId)
if (vendor) {
dispatch({
type: 'UPDATE_VENDOR',
payload: {
id: vendorId,
data: { contracts: [...vendor.contracts, contract.id] },
},
})
}
return contract
}
export async function apiUpdateContract(
dispatch: Dispatch,
id: string,
data: Partial<ContractDocument>
): Promise<void> {
const response = await fetch(`${API_BASE}/contracts/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Aktualisieren des Vertrags')
}
dispatch({ type: 'UPDATE_CONTRACT', payload: { id, data } })
}
export async function apiDeleteContract(
dispatch: Dispatch,
state: VendorComplianceState,
id: string
): Promise<void> {
const contract = state.contracts.find((c) => c.id === id)
const response = await fetch(`${API_BASE}/contracts/${id}`, {
method: 'DELETE',
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Loeschen des Vertrags')
}
dispatch({ type: 'DELETE_CONTRACT', payload: id })
// Update vendor's contracts list
if (contract) {
const vendor = state.vendors.find((v) => v.id === contract.vendorId)
if (vendor) {
dispatch({
type: 'UPDATE_VENDOR',
payload: {
id: vendor.id,
data: { contracts: vendor.contracts.filter((cId) => cId !== id) },
},
})
}
}
}
export async function apiStartContractReview(
dispatch: Dispatch,
contractId: string
): Promise<void> {
dispatch({
type: 'UPDATE_CONTRACT',
payload: { id: contractId, data: { reviewStatus: 'IN_PROGRESS' } },
})
const response = await fetch(`${API_BASE}/contracts/${contractId}/review`, {
method: 'POST',
})
if (!response.ok) {
dispatch({
type: 'UPDATE_CONTRACT',
payload: { id: contractId, data: { reviewStatus: 'FAILED' } },
})
const error = await response.json()
throw new Error(error.error || 'Fehler beim Starten der Vertragspruefung')
}
const result = await response.json()
// Update contract with review results
dispatch({
type: 'UPDATE_CONTRACT',
payload: {
id: contractId,
data: {
reviewStatus: 'COMPLETED',
reviewCompletedAt: new Date(),
complianceScore: result.data.complianceScore,
},
},
})
// Add findings
if (result.data.findings && result.data.findings.length > 0) {
dispatch({ type: 'ADD_FINDINGS', payload: result.data.findings })
}
}
// ==========================================
// FINDINGS ACTIONS
// ==========================================
export async function apiUpdateFinding(
dispatch: Dispatch,
id: string,
data: Partial<Finding>
): Promise<void> {
const response = await fetch(`${API_BASE}/findings/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Aktualisieren des Findings')
}
dispatch({ type: 'UPDATE_FINDING', payload: { id, data } })
}
export async function apiResolveFinding(
dispatch: Dispatch,
id: string,
resolution: string
): Promise<void> {
await apiUpdateFinding(dispatch, id, {
status: 'RESOLVED',
resolution,
resolvedAt: new Date(),
})
}
// ==========================================
// CONTROL ACTIONS
// ==========================================
export async function apiUpdateControlInstance(
dispatch: Dispatch,
id: string,
data: Partial<ControlInstance>
): Promise<void> {
const response = await fetch(`${API_BASE}/control-instances/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Aktualisieren des Control-Status')
}
dispatch({ type: 'UPDATE_CONTROL_INSTANCE', payload: { id, data } })
}
// ==========================================
// EXPORT ACTIONS
// ==========================================
export async function apiExportVVT(
format: ExportFormat,
activityIds?: string[]
): Promise<string> {
const params = new URLSearchParams({ format })
if (activityIds && activityIds.length > 0) {
params.append('activityIds', activityIds.join(','))
}
const response = await fetch(`${API_BASE}/export/vvt?${params}`)
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Exportieren des VVT')
}
const blob = await response.blob()
const url = URL.createObjectURL(blob)
return url
}
export async function apiExportVendorAuditPack(
vendorId: string,
format: ExportFormat
): Promise<string> {
const params = new URLSearchParams({ format, vendorId })
const response = await fetch(`${API_BASE}/export/vendor-audit?${params}`)
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Exportieren des Vendor Audit Packs')
}
const blob = await response.blob()
const url = URL.createObjectURL(blob)
return url
}
export async function apiExportRoPA(
format: ExportFormat
): Promise<string> {
const params = new URLSearchParams({ format })
const response = await fetch(`${API_BASE}/export/ropa?${params}`)
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Exportieren des RoPA')
}
const blob = await response.blob()
const url = URL.createObjectURL(blob)
return url
}

View File

@@ -0,0 +1,334 @@
import type {
VendorComplianceState,
VendorComplianceAction,
VendorStatistics,
ComplianceStatistics,
RiskOverview,
VendorStatus,
VendorRole,
RiskLevel,
FindingType,
FindingSeverity,
} from './types'
import { getRiskLevelFromScore } from './types'
// ==========================================
// INITIAL STATE
// ==========================================
export const initialState: VendorComplianceState = {
processingActivities: [],
vendors: [],
contracts: [],
findings: [],
controls: [],
controlInstances: [],
riskAssessments: [],
isLoading: false,
error: null,
selectedVendorId: null,
selectedActivityId: null,
activeTab: 'overview',
lastModified: null,
}
// ==========================================
// REDUCER
// ==========================================
export function vendorComplianceReducer(
state: VendorComplianceState,
action: VendorComplianceAction
): VendorComplianceState {
const updateState = (updates: Partial<VendorComplianceState>): VendorComplianceState => ({
...state,
...updates,
lastModified: new Date(),
})
switch (action.type) {
// Processing Activities
case 'SET_PROCESSING_ACTIVITIES':
return updateState({ processingActivities: action.payload })
case 'ADD_PROCESSING_ACTIVITY':
return updateState({
processingActivities: [...state.processingActivities, action.payload],
})
case 'UPDATE_PROCESSING_ACTIVITY':
return updateState({
processingActivities: state.processingActivities.map((activity) =>
activity.id === action.payload.id
? { ...activity, ...action.payload.data, updatedAt: new Date() }
: activity
),
})
case 'DELETE_PROCESSING_ACTIVITY':
return updateState({
processingActivities: state.processingActivities.filter(
(activity) => activity.id !== action.payload
),
})
// Vendors
case 'SET_VENDORS':
return updateState({ vendors: action.payload })
case 'ADD_VENDOR':
return updateState({
vendors: [...state.vendors, action.payload],
})
case 'UPDATE_VENDOR':
return updateState({
vendors: state.vendors.map((vendor) =>
vendor.id === action.payload.id
? { ...vendor, ...action.payload.data, updatedAt: new Date() }
: vendor
),
})
case 'DELETE_VENDOR':
return updateState({
vendors: state.vendors.filter((vendor) => vendor.id !== action.payload),
})
// Contracts
case 'SET_CONTRACTS':
return updateState({ contracts: action.payload })
case 'ADD_CONTRACT':
return updateState({
contracts: [...state.contracts, action.payload],
})
case 'UPDATE_CONTRACT':
return updateState({
contracts: state.contracts.map((contract) =>
contract.id === action.payload.id
? { ...contract, ...action.payload.data, updatedAt: new Date() }
: contract
),
})
case 'DELETE_CONTRACT':
return updateState({
contracts: state.contracts.filter((contract) => contract.id !== action.payload),
})
// Findings
case 'SET_FINDINGS':
return updateState({ findings: action.payload })
case 'ADD_FINDINGS':
return updateState({
findings: [...state.findings, ...action.payload],
})
case 'UPDATE_FINDING':
return updateState({
findings: state.findings.map((finding) =>
finding.id === action.payload.id
? { ...finding, ...action.payload.data, updatedAt: new Date() }
: finding
),
})
// Controls
case 'SET_CONTROLS':
return updateState({ controls: action.payload })
case 'SET_CONTROL_INSTANCES':
return updateState({ controlInstances: action.payload })
case 'UPDATE_CONTROL_INSTANCE':
return updateState({
controlInstances: state.controlInstances.map((instance) =>
instance.id === action.payload.id
? { ...instance, ...action.payload.data }
: instance
),
})
// Risk Assessments
case 'SET_RISK_ASSESSMENTS':
return updateState({ riskAssessments: action.payload })
case 'UPDATE_RISK_ASSESSMENT':
return updateState({
riskAssessments: state.riskAssessments.map((assessment) =>
assessment.id === action.payload.id
? { ...assessment, ...action.payload.data }
: assessment
),
})
// UI State
case 'SET_LOADING':
return { ...state, isLoading: action.payload }
case 'SET_ERROR':
return { ...state, error: action.payload }
case 'SET_SELECTED_VENDOR':
return { ...state, selectedVendorId: action.payload }
case 'SET_SELECTED_ACTIVITY':
return { ...state, selectedActivityId: action.payload }
case 'SET_ACTIVE_TAB':
return { ...state, activeTab: action.payload }
default:
return state
}
}
// ==========================================
// COMPUTED VALUES (pure functions of state)
// ==========================================
export function computeVendorStats(state: VendorComplianceState): VendorStatistics {
const vendors = state.vendors
const byStatus = vendors.reduce(
(acc, v) => {
acc[v.status] = (acc[v.status] || 0) + 1
return acc
},
{} as Record<VendorStatus, number>
)
const byRole = vendors.reduce(
(acc, v) => {
acc[v.role] = (acc[v.role] || 0) + 1
return acc
},
{} as Record<VendorRole, number>
)
const byRiskLevel = vendors.reduce(
(acc, v) => {
const level = getRiskLevelFromScore(v.residualRiskScore / 4) // Normalize to 1-25
acc[level] = (acc[level] || 0) + 1
return acc
},
{} as Record<RiskLevel, number>
)
const now = new Date()
const pendingReviews = vendors.filter(
(v) => v.nextReviewDate && new Date(v.nextReviewDate) <= now
).length
const withExpiredContracts = vendors.filter((v) =>
state.contracts.some(
(c) =>
c.vendorId === v.id &&
c.expirationDate &&
new Date(c.expirationDate) <= now &&
c.status === 'ACTIVE'
)
).length
return {
total: vendors.length,
byStatus,
byRole,
byRiskLevel,
pendingReviews,
withExpiredContracts,
}
}
export function computeComplianceStats(state: VendorComplianceState): ComplianceStatistics {
const findings = state.findings
const contracts = state.contracts
const controlInstances = state.controlInstances
const averageComplianceScore =
contracts.length > 0
? contracts.reduce((sum, c) => sum + (c.complianceScore || 0), 0) /
contracts.filter((c) => c.complianceScore !== undefined).length || 0
: 0
const findingsByType = findings.reduce(
(acc, f) => {
acc[f.type] = (acc[f.type] || 0) + 1
return acc
},
{} as Record<FindingType, number>
)
const findingsBySeverity = findings.reduce(
(acc, f) => {
acc[f.severity] = (acc[f.severity] || 0) + 1
return acc
},
{} as Record<FindingSeverity, number>
)
const openFindings = findings.filter(
(f) => f.status === 'OPEN' || f.status === 'IN_PROGRESS'
).length
const resolvedFindings = findings.filter(
(f) => f.status === 'RESOLVED' || f.status === 'FALSE_POSITIVE'
).length
const passedControls = controlInstances.filter(
(ci) => ci.status === 'PASS'
).length
const applicableControls = controlInstances.filter(
(ci) => ci.status !== 'NOT_APPLICABLE'
).length
const controlPassRate =
applicableControls > 0 ? (passedControls / applicableControls) * 100 : 0
return {
averageComplianceScore,
findingsByType,
findingsBySeverity,
openFindings,
resolvedFindings,
controlPassRate,
}
}
export function computeRiskOverview(state: VendorComplianceState): RiskOverview {
const vendors = state.vendors
const findings = state.findings
const averageInherentRisk =
vendors.length > 0
? vendors.reduce((sum, v) => sum + v.inherentRiskScore, 0) / vendors.length
: 0
const averageResidualRisk =
vendors.length > 0
? vendors.reduce((sum, v) => sum + v.residualRiskScore, 0) / vendors.length
: 0
const highRiskVendors = vendors.filter(
(v) => v.residualRiskScore >= 60
).length
const criticalFindings = findings.filter(
(f) => f.severity === 'CRITICAL' && f.status === 'OPEN'
).length
const transfersToThirdCountries = vendors.filter((v) =>
v.processingLocations.some((pl) => !pl.isEU && !pl.isAdequate)
).length
return {
averageInherentRisk,
averageResidualRisk,
highRiskVendors,
criticalFindings,
transfersToThirdCountries,
}
}

View File

@@ -10,202 +10,44 @@ import React, {
useState,
} from 'react'
import {
VendorComplianceState,
VendorComplianceAction,
import type {
VendorComplianceContextValue,
ProcessingActivity,
Vendor,
ContractDocument,
Finding,
Control,
ControlInstance,
RiskAssessment,
VendorStatistics,
ComplianceStatistics,
RiskOverview,
ExportFormat,
VendorStatus,
VendorRole,
RiskLevel,
FindingType,
FindingSeverity,
getRiskLevelFromScore,
} from './types'
// ==========================================
// INITIAL STATE
// ==========================================
import {
vendorComplianceReducer,
initialState,
computeVendorStats,
computeComplianceStats,
computeRiskOverview,
} from './context-reducer'
const initialState: VendorComplianceState = {
processingActivities: [],
vendors: [],
contracts: [],
findings: [],
controls: [],
controlInstances: [],
riskAssessments: [],
isLoading: false,
error: null,
selectedVendorId: null,
selectedActivityId: null,
activeTab: 'overview',
lastModified: null,
}
// ==========================================
// REDUCER
// ==========================================
function vendorComplianceReducer(
state: VendorComplianceState,
action: VendorComplianceAction
): VendorComplianceState {
const updateState = (updates: Partial<VendorComplianceState>): VendorComplianceState => ({
...state,
...updates,
lastModified: new Date(),
})
switch (action.type) {
// Processing Activities
case 'SET_PROCESSING_ACTIVITIES':
return updateState({ processingActivities: action.payload })
case 'ADD_PROCESSING_ACTIVITY':
return updateState({
processingActivities: [...state.processingActivities, action.payload],
})
case 'UPDATE_PROCESSING_ACTIVITY':
return updateState({
processingActivities: state.processingActivities.map((activity) =>
activity.id === action.payload.id
? { ...activity, ...action.payload.data, updatedAt: new Date() }
: activity
),
})
case 'DELETE_PROCESSING_ACTIVITY':
return updateState({
processingActivities: state.processingActivities.filter(
(activity) => activity.id !== action.payload
),
})
// Vendors
case 'SET_VENDORS':
return updateState({ vendors: action.payload })
case 'ADD_VENDOR':
return updateState({
vendors: [...state.vendors, action.payload],
})
case 'UPDATE_VENDOR':
return updateState({
vendors: state.vendors.map((vendor) =>
vendor.id === action.payload.id
? { ...vendor, ...action.payload.data, updatedAt: new Date() }
: vendor
),
})
case 'DELETE_VENDOR':
return updateState({
vendors: state.vendors.filter((vendor) => vendor.id !== action.payload),
})
// Contracts
case 'SET_CONTRACTS':
return updateState({ contracts: action.payload })
case 'ADD_CONTRACT':
return updateState({
contracts: [...state.contracts, action.payload],
})
case 'UPDATE_CONTRACT':
return updateState({
contracts: state.contracts.map((contract) =>
contract.id === action.payload.id
? { ...contract, ...action.payload.data, updatedAt: new Date() }
: contract
),
})
case 'DELETE_CONTRACT':
return updateState({
contracts: state.contracts.filter((contract) => contract.id !== action.payload),
})
// Findings
case 'SET_FINDINGS':
return updateState({ findings: action.payload })
case 'ADD_FINDINGS':
return updateState({
findings: [...state.findings, ...action.payload],
})
case 'UPDATE_FINDING':
return updateState({
findings: state.findings.map((finding) =>
finding.id === action.payload.id
? { ...finding, ...action.payload.data, updatedAt: new Date() }
: finding
),
})
// Controls
case 'SET_CONTROLS':
return updateState({ controls: action.payload })
case 'SET_CONTROL_INSTANCES':
return updateState({ controlInstances: action.payload })
case 'UPDATE_CONTROL_INSTANCE':
return updateState({
controlInstances: state.controlInstances.map((instance) =>
instance.id === action.payload.id
? { ...instance, ...action.payload.data }
: instance
),
})
// Risk Assessments
case 'SET_RISK_ASSESSMENTS':
return updateState({ riskAssessments: action.payload })
case 'UPDATE_RISK_ASSESSMENT':
return updateState({
riskAssessments: state.riskAssessments.map((assessment) =>
assessment.id === action.payload.id
? { ...assessment, ...action.payload.data }
: assessment
),
})
// UI State
case 'SET_LOADING':
return { ...state, isLoading: action.payload }
case 'SET_ERROR':
return { ...state, error: action.payload }
case 'SET_SELECTED_VENDOR':
return { ...state, selectedVendorId: action.payload }
case 'SET_SELECTED_ACTIVITY':
return { ...state, selectedActivityId: action.payload }
case 'SET_ACTIVE_TAB':
return { ...state, activeTab: action.payload }
default:
return state
}
}
import {
loadAllData,
apiCreateProcessingActivity,
apiUpdateProcessingActivity,
apiDeleteProcessingActivity,
apiDuplicateProcessingActivity,
apiCreateVendor,
apiUpdateVendor,
apiDeleteVendor,
apiUploadContract,
apiUpdateContract,
apiDeleteContract,
apiStartContractReview,
apiUpdateFinding,
apiResolveFinding,
apiUpdateControlInstance,
apiExportVVT,
apiExportVendorAuditPack,
apiExportRoPA,
} from './context-actions'
// ==========================================
// CONTEXT
@@ -233,626 +75,150 @@ export function VendorComplianceProvider({
// COMPUTED VALUES
// ==========================================
const vendorStats = useMemo<VendorStatistics>(() => {
const vendors = state.vendors
const vendorStats = useMemo(
() => computeVendorStats(state),
[state.vendors, state.contracts]
)
const byStatus = vendors.reduce(
(acc, v) => {
acc[v.status] = (acc[v.status] || 0) + 1
return acc
},
{} as Record<VendorStatus, number>
)
const complianceStats = useMemo(
() => computeComplianceStats(state),
[state.findings, state.contracts, state.controlInstances]
)
const byRole = vendors.reduce(
(acc, v) => {
acc[v.role] = (acc[v.role] || 0) + 1
return acc
},
{} as Record<VendorRole, number>
)
const byRiskLevel = vendors.reduce(
(acc, v) => {
const level = getRiskLevelFromScore(v.residualRiskScore / 4) // Normalize to 1-25
acc[level] = (acc[level] || 0) + 1
return acc
},
{} as Record<RiskLevel, number>
)
const now = new Date()
const pendingReviews = vendors.filter(
(v) => v.nextReviewDate && new Date(v.nextReviewDate) <= now
).length
const withExpiredContracts = vendors.filter((v) =>
state.contracts.some(
(c) =>
c.vendorId === v.id &&
c.expirationDate &&
new Date(c.expirationDate) <= now &&
c.status === 'ACTIVE'
)
).length
return {
total: vendors.length,
byStatus,
byRole,
byRiskLevel,
pendingReviews,
withExpiredContracts,
}
}, [state.vendors, state.contracts])
const complianceStats = useMemo<ComplianceStatistics>(() => {
const findings = state.findings
const contracts = state.contracts
const controlInstances = state.controlInstances
const averageComplianceScore =
contracts.length > 0
? contracts.reduce((sum, c) => sum + (c.complianceScore || 0), 0) /
contracts.filter((c) => c.complianceScore !== undefined).length || 0
: 0
const findingsByType = findings.reduce(
(acc, f) => {
acc[f.type] = (acc[f.type] || 0) + 1
return acc
},
{} as Record<FindingType, number>
)
const findingsBySeverity = findings.reduce(
(acc, f) => {
acc[f.severity] = (acc[f.severity] || 0) + 1
return acc
},
{} as Record<FindingSeverity, number>
)
const openFindings = findings.filter(
(f) => f.status === 'OPEN' || f.status === 'IN_PROGRESS'
).length
const resolvedFindings = findings.filter(
(f) => f.status === 'RESOLVED' || f.status === 'FALSE_POSITIVE'
).length
const passedControls = controlInstances.filter(
(ci) => ci.status === 'PASS'
).length
const applicableControls = controlInstances.filter(
(ci) => ci.status !== 'NOT_APPLICABLE'
).length
const controlPassRate =
applicableControls > 0 ? (passedControls / applicableControls) * 100 : 0
return {
averageComplianceScore,
findingsByType,
findingsBySeverity,
openFindings,
resolvedFindings,
controlPassRate,
}
}, [state.findings, state.contracts, state.controlInstances])
const riskOverview = useMemo<RiskOverview>(() => {
const vendors = state.vendors
const findings = state.findings
const averageInherentRisk =
vendors.length > 0
? vendors.reduce((sum, v) => sum + v.inherentRiskScore, 0) / vendors.length
: 0
const averageResidualRisk =
vendors.length > 0
? vendors.reduce((sum, v) => sum + v.residualRiskScore, 0) / vendors.length
: 0
const highRiskVendors = vendors.filter(
(v) => v.residualRiskScore >= 60
).length
const criticalFindings = findings.filter(
(f) => f.severity === 'CRITICAL' && f.status === 'OPEN'
).length
const transfersToThirdCountries = vendors.filter((v) =>
v.processingLocations.some((pl) => !pl.isEU && !pl.isAdequate)
).length
return {
averageInherentRisk,
averageResidualRisk,
highRiskVendors,
criticalFindings,
transfersToThirdCountries,
}
}, [state.vendors, state.findings])
const riskOverview = useMemo(
() => computeRiskOverview(state),
[state.vendors, state.findings]
)
// ==========================================
// API CALLS
// ACTION WRAPPERS
// ==========================================
const apiBase = '/api/sdk/v1/vendor-compliance'
const loadData = useCallback(async () => {
dispatch({ type: 'SET_LOADING', payload: true })
dispatch({ type: 'SET_ERROR', payload: null })
try {
const [
activitiesRes,
vendorsRes,
contractsRes,
findingsRes,
controlsRes,
controlInstancesRes,
] = await Promise.all([
fetch(`${apiBase}/processing-activities`),
fetch(`${apiBase}/vendors`),
fetch(`${apiBase}/contracts`),
fetch(`${apiBase}/findings`),
fetch(`${apiBase}/controls`),
fetch(`${apiBase}/control-instances`),
])
if (activitiesRes.ok) {
const data = await activitiesRes.json()
dispatch({ type: 'SET_PROCESSING_ACTIVITIES', payload: data.data || [] })
}
if (vendorsRes.ok) {
const data = await vendorsRes.json()
dispatch({ type: 'SET_VENDORS', payload: data.data || [] })
}
if (contractsRes.ok) {
const data = await contractsRes.json()
dispatch({ type: 'SET_CONTRACTS', payload: data.data || [] })
}
if (findingsRes.ok) {
const data = await findingsRes.json()
dispatch({ type: 'SET_FINDINGS', payload: data.data || [] })
}
if (controlsRes.ok) {
const data = await controlsRes.json()
dispatch({ type: 'SET_CONTROLS', payload: data.data || [] })
}
if (controlInstancesRes.ok) {
const data = await controlInstancesRes.json()
dispatch({ type: 'SET_CONTROL_INSTANCES', payload: data.data || [] })
}
} catch (error) {
console.error('Failed to load vendor compliance data:', error)
dispatch({
type: 'SET_ERROR',
payload: 'Fehler beim Laden der Daten',
})
} finally {
dispatch({ type: 'SET_LOADING', payload: false })
}
}, [apiBase])
await loadAllData(dispatch)
}, [])
const refresh = useCallback(async () => {
await loadData()
}, [loadData])
// ==========================================
// PROCESSING ACTIVITIES ACTIONS
// ==========================================
const createProcessingActivity = useCallback(
async (
data: Omit<ProcessingActivity, 'id' | 'tenantId' | 'createdAt' | 'updatedAt'>
): Promise<ProcessingActivity> => {
const response = await fetch(`${apiBase}/processing-activities`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Erstellen der Verarbeitungstätigkeit')
}
const result = await response.json()
const activity = result.data
dispatch({ type: 'ADD_PROCESSING_ACTIVITY', payload: activity })
return activity
async (data: Omit<ProcessingActivity, 'id' | 'tenantId' | 'createdAt' | 'updatedAt'>) => {
return apiCreateProcessingActivity(dispatch, data)
},
[apiBase]
[]
)
const updateProcessingActivity = useCallback(
async (id: string, data: Partial<ProcessingActivity>): Promise<void> => {
const response = await fetch(`${apiBase}/processing-activities/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Aktualisieren der Verarbeitungstätigkeit')
}
dispatch({ type: 'UPDATE_PROCESSING_ACTIVITY', payload: { id, data } })
async (id: string, data: Partial<ProcessingActivity>) => {
await apiUpdateProcessingActivity(dispatch, id, data)
},
[apiBase]
[]
)
const deleteProcessingActivity = useCallback(
async (id: string): Promise<void> => {
const response = await fetch(`${apiBase}/processing-activities/${id}`, {
method: 'DELETE',
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Löschen der Verarbeitungstätigkeit')
}
dispatch({ type: 'DELETE_PROCESSING_ACTIVITY', payload: id })
async (id: string) => {
await apiDeleteProcessingActivity(dispatch, id)
},
[apiBase]
[]
)
const duplicateProcessingActivity = useCallback(
async (id: string): Promise<ProcessingActivity> => {
const original = state.processingActivities.find((a) => a.id === id)
if (!original) {
throw new Error('Verarbeitungstätigkeit nicht gefunden')
}
const { id: _id, vvtId: _vvtId, createdAt: _createdAt, updatedAt: _updatedAt, tenantId: _tenantId, ...rest } = original
const newActivity = await createProcessingActivity({
...rest,
vvtId: '', // Will be generated by backend
name: {
de: `${original.name.de} (Kopie)`,
en: `${original.name.en} (Copy)`,
},
status: 'DRAFT',
})
return newActivity
async (id: string) => {
return apiDuplicateProcessingActivity(dispatch, state, id)
},
[state.processingActivities, createProcessingActivity]
[state]
)
// ==========================================
// VENDOR ACTIONS
// ==========================================
const createVendor = useCallback(
async (
data: Omit<Vendor, 'id' | 'tenantId' | 'createdAt' | 'updatedAt'>
): Promise<Vendor> => {
const response = await fetch(`${apiBase}/vendors`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Erstellen des Vendors')
}
const result = await response.json()
const vendor = result.data
dispatch({ type: 'ADD_VENDOR', payload: vendor })
return vendor
async (data: Omit<Vendor, 'id' | 'tenantId' | 'createdAt' | 'updatedAt'>) => {
return apiCreateVendor(dispatch, data)
},
[apiBase]
[]
)
const updateVendor = useCallback(
async (id: string, data: Partial<Vendor>): Promise<void> => {
const response = await fetch(`${apiBase}/vendors/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Aktualisieren des Vendors')
}
dispatch({ type: 'UPDATE_VENDOR', payload: { id, data } })
async (id: string, data: Partial<Vendor>) => {
await apiUpdateVendor(dispatch, id, data)
},
[apiBase]
[]
)
const deleteVendor = useCallback(
async (id: string): Promise<void> => {
const response = await fetch(`${apiBase}/vendors/${id}`, {
method: 'DELETE',
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Löschen des Vendors')
}
dispatch({ type: 'DELETE_VENDOR', payload: id })
async (id: string) => {
await apiDeleteVendor(dispatch, id)
},
[apiBase]
[]
)
// ==========================================
// CONTRACT ACTIONS
// ==========================================
const uploadContract = useCallback(
async (
vendorId: string,
file: File,
metadata: Partial<ContractDocument>
): Promise<ContractDocument> => {
const formData = new FormData()
formData.append('file', file)
formData.append('vendorId', vendorId)
formData.append('metadata', JSON.stringify(metadata))
const response = await fetch(`${apiBase}/contracts`, {
method: 'POST',
body: formData,
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Hochladen des Vertrags')
}
const result = await response.json()
const contract = result.data
dispatch({ type: 'ADD_CONTRACT', payload: contract })
// Update vendor's contracts list
const vendor = state.vendors.find((v) => v.id === vendorId)
if (vendor) {
dispatch({
type: 'UPDATE_VENDOR',
payload: {
id: vendorId,
data: { contracts: [...vendor.contracts, contract.id] },
},
})
}
return contract
async (vendorId: string, file: File, metadata: Partial<ContractDocument>) => {
return apiUploadContract(dispatch, state, vendorId, file, metadata)
},
[apiBase, state.vendors]
[state]
)
const updateContract = useCallback(
async (id: string, data: Partial<ContractDocument>): Promise<void> => {
const response = await fetch(`${apiBase}/contracts/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Aktualisieren des Vertrags')
}
dispatch({ type: 'UPDATE_CONTRACT', payload: { id, data } })
async (id: string, data: Partial<ContractDocument>) => {
await apiUpdateContract(dispatch, id, data)
},
[apiBase]
[]
)
const deleteContract = useCallback(
async (id: string): Promise<void> => {
const contract = state.contracts.find((c) => c.id === id)
const response = await fetch(`${apiBase}/contracts/${id}`, {
method: 'DELETE',
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Löschen des Vertrags')
}
dispatch({ type: 'DELETE_CONTRACT', payload: id })
// Update vendor's contracts list
if (contract) {
const vendor = state.vendors.find((v) => v.id === contract.vendorId)
if (vendor) {
dispatch({
type: 'UPDATE_VENDOR',
payload: {
id: vendor.id,
data: { contracts: vendor.contracts.filter((cId) => cId !== id) },
},
})
}
}
async (id: string) => {
await apiDeleteContract(dispatch, state, id)
},
[apiBase, state.contracts, state.vendors]
[state]
)
const startContractReview = useCallback(
async (contractId: string): Promise<void> => {
dispatch({
type: 'UPDATE_CONTRACT',
payload: { id: contractId, data: { reviewStatus: 'IN_PROGRESS' } },
})
const response = await fetch(`${apiBase}/contracts/${contractId}/review`, {
method: 'POST',
})
if (!response.ok) {
dispatch({
type: 'UPDATE_CONTRACT',
payload: { id: contractId, data: { reviewStatus: 'FAILED' } },
})
const error = await response.json()
throw new Error(error.error || 'Fehler beim Starten der Vertragsprüfung')
}
const result = await response.json()
// Update contract with review results
dispatch({
type: 'UPDATE_CONTRACT',
payload: {
id: contractId,
data: {
reviewStatus: 'COMPLETED',
reviewCompletedAt: new Date(),
complianceScore: result.data.complianceScore,
},
},
})
// Add findings
if (result.data.findings && result.data.findings.length > 0) {
dispatch({ type: 'ADD_FINDINGS', payload: result.data.findings })
}
async (contractId: string) => {
await apiStartContractReview(dispatch, contractId)
},
[apiBase]
[]
)
// ==========================================
// FINDINGS ACTIONS
// ==========================================
const updateFinding = useCallback(
async (id: string, data: Partial<Finding>): Promise<void> => {
const response = await fetch(`${apiBase}/findings/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Aktualisieren des Findings')
}
dispatch({ type: 'UPDATE_FINDING', payload: { id, data } })
async (id: string, data: Partial<Finding>) => {
await apiUpdateFinding(dispatch, id, data)
},
[apiBase]
[]
)
const resolveFinding = useCallback(
async (id: string, resolution: string): Promise<void> => {
await updateFinding(id, {
status: 'RESOLVED',
resolution,
resolvedAt: new Date(),
})
async (id: string, resolution: string) => {
await apiResolveFinding(dispatch, id, resolution)
},
[updateFinding]
[]
)
// ==========================================
// CONTROL ACTIONS
// ==========================================
const updateControlInstance = useCallback(
async (id: string, data: Partial<ControlInstance>): Promise<void> => {
const response = await fetch(`${apiBase}/control-instances/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Aktualisieren des Control-Status')
}
dispatch({ type: 'UPDATE_CONTROL_INSTANCE', payload: { id, data } })
async (id: string, data: Partial<ControlInstance>) => {
await apiUpdateControlInstance(dispatch, id, data)
},
[apiBase]
[]
)
// ==========================================
// EXPORT ACTIONS
// ==========================================
const exportVVT = useCallback(
async (format: ExportFormat, activityIds?: string[]): Promise<string> => {
const params = new URLSearchParams({ format })
if (activityIds && activityIds.length > 0) {
params.append('activityIds', activityIds.join(','))
}
const response = await fetch(`${apiBase}/export/vvt?${params}`)
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Exportieren des VVT')
}
const blob = await response.blob()
const url = URL.createObjectURL(blob)
return url
async (format: ExportFormat, activityIds?: string[]) => {
return apiExportVVT(format, activityIds)
},
[apiBase]
[]
)
const exportVendorAuditPack = useCallback(
async (vendorId: string, format: ExportFormat): Promise<string> => {
const params = new URLSearchParams({ format, vendorId })
const response = await fetch(`${apiBase}/export/vendor-audit?${params}`)
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Exportieren des Vendor Audit Packs')
}
const blob = await response.blob()
const url = URL.createObjectURL(blob)
return url
async (vendorId: string, format: ExportFormat) => {
return apiExportVendorAuditPack(vendorId, format)
},
[apiBase]
[]
)
const exportRoPA = useCallback(
async (format: ExportFormat): Promise<string> => {
const params = new URLSearchParams({ format })
const response = await fetch(`${apiBase}/export/ropa?${params}`)
if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Fehler beim Exportieren des RoPA')
}
const blob = await response.blob()
const url = URL.createObjectURL(blob)
return url
async (format: ExportFormat) => {
return apiExportRoPA(format)
},
[apiBase]
[]
)
// ==========================================

View File

@@ -0,0 +1,37 @@
/**
* Controls Library - Merged Array
*
* Assembles all domain-specific control arrays into the single CONTROLS_LIBRARY.
* This file exists to avoid circular imports between the barrel and helpers.
*/
import { Control } from '../types'
import {
TRANSFER_CONTROLS,
AUDIT_CONTROLS,
DELETION_CONTROLS,
INCIDENT_CONTROLS,
} from './controls-data-compliance'
import {
SUBPROCESSOR_CONTROLS,
TOM_CONTROLS,
CONTRACT_CONTROLS,
DATA_SUBJECT_CONTROLS,
SECURITY_CONTROLS,
GOVERNANCE_CONTROLS,
} from './controls-data-operations'
export const CONTROLS_LIBRARY: Control[] = [
...TRANSFER_CONTROLS,
...AUDIT_CONTROLS,
...DELETION_CONTROLS,
...INCIDENT_CONTROLS,
...SUBPROCESSOR_CONTROLS,
...TOM_CONTROLS,
...CONTRACT_CONTROLS,
...DATA_SUBJECT_CONTROLS,
...SECURITY_CONTROLS,
...GOVERNANCE_CONTROLS,
]

View File

@@ -0,0 +1,377 @@
/**
* Control Definitions - Compliance Domains
*
* Controls for: TRANSFER, AUDIT, DELETION, INCIDENT
*/
import { Control } from '../types'
// ==========================================
// TRANSFER - Drittlandtransfer Controls
// ==========================================
export const TRANSFER_CONTROLS: Control[] = [
{
id: 'VND-TRF-01',
domain: 'TRANSFER',
title: {
de: 'Drittlandtransfer nur mit Rechtsgrundlage',
en: 'Third country transfer with legal basis',
},
description: {
de: 'Drittlandtransfers erfolgen nur auf Basis von SCC, BCR oder Angemessenheitsbeschluss',
en: 'Third country transfers only based on SCC, BCR or adequacy decision',
},
passCriteria: {
de: 'SCC oder BCR vertraglich vereinbart ODER Angemessenheitsbeschluss vorhanden',
en: 'SCC or BCR contractually agreed OR adequacy decision exists',
},
requirements: ['Art. 44-49 DSGVO', 'ISO 27001 A.15.1.2'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-TRF-02',
domain: 'TRANSFER',
title: {
de: 'Aktuelle Standardvertragsklauseln',
en: 'Current Standard Contractual Clauses',
},
description: {
de: 'Bei SCC-Nutzung: Verwendung der aktuellen EU-Kommission-Klauseln (2021)',
en: 'When using SCC: Current EU Commission clauses (2021) are used',
},
passCriteria: {
de: 'SCC 2021 (Durchführungsbeschluss (EU) 2021/914) verwendet',
en: 'SCC 2021 (Implementing Decision (EU) 2021/914) used',
},
requirements: ['Art. 46 Abs. 2 lit. c DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-TRF-03',
domain: 'TRANSFER',
title: {
de: 'Transfer Impact Assessment (TIA)',
en: 'Transfer Impact Assessment (TIA)',
},
description: {
de: 'Bei Transfers in Drittländer ohne Angemessenheitsbeschluss ist TIA durchzuführen',
en: 'TIA required for transfers to third countries without adequacy decision',
},
passCriteria: {
de: 'TIA dokumentiert und bewertet Risiken als akzeptabel',
en: 'TIA documented and risks assessed as acceptable',
},
requirements: ['Schrems II Urteil', 'EDSA Empfehlungen 01/2020'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-TRF-04',
domain: 'TRANSFER',
title: {
de: 'Zusätzliche Schutzmaßnahmen',
en: 'Supplementary Measures',
},
description: {
de: 'Bei Bedarf sind zusätzliche technische/organisatorische Maßnahmen implementiert',
en: 'Supplementary technical/organizational measures implemented where needed',
},
passCriteria: {
de: 'Ergänzende Maßnahmen dokumentiert (Verschlüsselung, Pseudonymisierung, etc.)',
en: 'Supplementary measures documented (encryption, pseudonymization, etc.)',
},
requirements: ['EDSA Empfehlungen 01/2020'],
isRequired: false,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-TRF-05',
domain: 'TRANSFER',
title: {
de: 'Überwachung Angemessenheitsbeschlüsse',
en: 'Monitoring Adequacy Decisions',
},
description: {
de: 'Änderungen bei Angemessenheitsbeschlüssen werden überwacht',
en: 'Changes to adequacy decisions are monitored',
},
passCriteria: {
de: 'Prozess zur Überwachung und Reaktion auf Änderungen etabliert',
en: 'Process for monitoring and responding to changes established',
},
requirements: ['Art. 45 DSGVO'],
isRequired: false,
defaultFrequency: 'QUARTERLY',
},
]
// ==========================================
// AUDIT - Auditrechte Controls
// ==========================================
export const AUDIT_CONTROLS: Control[] = [
{
id: 'VND-AUD-01',
domain: 'AUDIT',
title: {
de: 'Auditrecht vertraglich vereinbart',
en: 'Audit right contractually agreed',
},
description: {
de: 'Vertrag enthält wirksames Auditrecht ohne unangemessene Einschränkungen',
en: 'Contract contains effective audit right without unreasonable restrictions',
},
passCriteria: {
de: 'Auditrecht im AVV enthalten, max. 30 Tage Vorlaufzeit, keine Ausschlussklausel',
en: 'Audit right in DPA, max 30 days notice, no exclusion clause',
},
requirements: ['Art. 28 Abs. 3 lit. h DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-AUD-02',
domain: 'AUDIT',
title: {
de: 'Vor-Ort-Inspektionen möglich',
en: 'On-site inspections possible',
},
description: {
de: 'Vertrag erlaubt Vor-Ort-Inspektionen bei dem Auftragsverarbeiter',
en: 'Contract allows on-site inspections at the processor',
},
passCriteria: {
de: 'Vor-Ort-Audit explizit erlaubt, Zugang zu relevanten Bereichen',
en: 'On-site audit explicitly allowed, access to relevant areas',
},
requirements: ['Art. 28 Abs. 3 lit. h DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-AUD-03',
domain: 'AUDIT',
title: {
de: 'Aktuelle Zertifizierungen',
en: 'Current Certifications',
},
description: {
de: 'Relevante Sicherheitszertifizierungen sind aktuell und gültig',
en: 'Relevant security certifications are current and valid',
},
passCriteria: {
de: 'ISO 27001, SOC 2 oder vergleichbar, nicht abgelaufen',
en: 'ISO 27001, SOC 2 or equivalent, not expired',
},
requirements: ['Art. 32 DSGVO', 'ISO 27001 A.15.1.1'],
isRequired: false,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-AUD-04',
domain: 'AUDIT',
title: {
de: 'Letzte Prüfung durchgeführt',
en: 'Last review conducted',
},
description: {
de: 'Vendor wurde innerhalb des Review-Zyklus geprüft',
en: 'Vendor was reviewed within the review cycle',
},
passCriteria: {
de: 'Dokumentierte Prüfung innerhalb des festgelegten Intervalls',
en: 'Documented review within the defined interval',
},
requirements: ['Art. 28 Abs. 3 lit. h DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-AUD-05',
domain: 'AUDIT',
title: {
de: 'Prüfberichte verfügbar',
en: 'Audit reports available',
},
description: {
de: 'Aktuelle Prüfberichte (SOC 2, Penetrationstest, etc.) liegen vor',
en: 'Current audit reports (SOC 2, penetration test, etc.) are available',
},
passCriteria: {
de: 'Prüfberichte nicht älter als 12 Monate',
en: 'Audit reports not older than 12 months',
},
requirements: ['ISO 27001 A.18.2.1'],
isRequired: false,
defaultFrequency: 'ANNUAL',
},
]
// ==========================================
// DELETION - Löschung Controls
// ==========================================
export const DELETION_CONTROLS: Control[] = [
{
id: 'VND-DEL-01',
domain: 'DELETION',
title: {
de: 'Löschung/Rückgabe nach Vertragsende',
en: 'Deletion/return after contract end',
},
description: {
de: 'Klare Regelung zur Löschung oder Rückgabe aller Daten nach Vertragsende',
en: 'Clear provision for deletion or return of all data after contract end',
},
passCriteria: {
de: 'Löschfrist max. 30 Tage, Löschbestätigung vorgesehen',
en: 'Deletion within max 30 days, deletion confirmation provided',
},
requirements: ['Art. 28 Abs. 3 lit. g DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-DEL-02',
domain: 'DELETION',
title: {
de: 'Löschbestätigung',
en: 'Deletion confirmation',
},
description: {
de: 'Schriftliche Bestätigung der vollständigen Datenlöschung',
en: 'Written confirmation of complete data deletion',
},
passCriteria: {
de: 'Löschbestätigung vertraglich vereinbart und einforderbar',
en: 'Deletion confirmation contractually agreed and enforceable',
},
requirements: ['Art. 28 Abs. 3 lit. g DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-DEL-03',
domain: 'DELETION',
title: {
de: 'Löschung bei Unterauftragnehmern',
en: 'Deletion at sub-processors',
},
description: {
de: 'Löschpflicht erstreckt sich auf alle Unterauftragnehmer',
en: 'Deletion obligation extends to all sub-processors',
},
passCriteria: {
de: 'Weitergabe der Löschpflicht an Unterauftragnehmer vertraglich vereinbart',
en: 'Transfer of deletion obligation to sub-processors contractually agreed',
},
requirements: ['Art. 28 Abs. 3 lit. g, d DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-DEL-04',
domain: 'DELETION',
title: {
de: 'Backup-Löschung',
en: 'Backup deletion',
},
description: {
de: 'Daten werden auch aus Backups gelöscht',
en: 'Data is also deleted from backups',
},
passCriteria: {
de: 'Backup-Löschung geregelt, max. Aufbewahrungsfrist für Backups definiert',
en: 'Backup deletion regulated, max retention period for backups defined',
},
requirements: ['Art. 28 Abs. 3 lit. g DSGVO'],
isRequired: false,
defaultFrequency: 'ANNUAL',
},
]
// ==========================================
// INCIDENT - Incident Response Controls
// ==========================================
export const INCIDENT_CONTROLS: Control[] = [
{
id: 'VND-INC-01',
domain: 'INCIDENT',
title: {
de: 'Meldepflicht bei Datenpannen',
en: 'Data breach notification obligation',
},
description: {
de: 'Unverzügliche Meldung von Datenschutzverletzungen',
en: 'Immediate notification of data protection violations',
},
passCriteria: {
de: 'Meldepflicht vereinbart, Frist max. 24-48h, Mindestinhalte definiert',
en: 'Notification obligation agreed, deadline max 24-48h, minimum content defined',
},
requirements: ['Art. 33 Abs. 2 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-INC-02',
domain: 'INCIDENT',
title: {
de: 'Incident Response Plan',
en: 'Incident Response Plan',
},
description: {
de: 'Vendor hat dokumentierten Incident Response Plan',
en: 'Vendor has documented incident response plan',
},
passCriteria: {
de: 'Incident Response Plan liegt vor und wurde getestet',
en: 'Incident response plan exists and has been tested',
},
requirements: ['ISO 27001 A.16.1'],
isRequired: false,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-INC-03',
domain: 'INCIDENT',
title: {
de: 'Kontaktstelle für Incidents',
en: 'Contact point for incidents',
},
description: {
de: 'Definierte Kontaktstelle für Datenschutzvorfälle',
en: 'Defined contact point for data protection incidents',
},
passCriteria: {
de: 'Kontaktdaten für Incident-Meldungen bekannt und aktuell',
en: 'Contact details for incident reporting known and current',
},
requirements: ['Art. 33 Abs. 2 DSGVO'],
isRequired: true,
defaultFrequency: 'QUARTERLY',
},
{
id: 'VND-INC-04',
domain: 'INCIDENT',
title: {
de: 'Unterstützung bei Incident-Dokumentation',
en: 'Support with incident documentation',
},
description: {
de: 'Vendor unterstützt bei der Dokumentation von Vorfällen',
en: 'Vendor supports documentation of incidents',
},
passCriteria: {
de: 'Unterstützungspflicht bei Dokumentation vertraglich vereinbart',
en: 'Support obligation for documentation contractually agreed',
},
requirements: ['Art. 33 Abs. 5 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
]

View File

@@ -0,0 +1,486 @@
/**
* Control Definitions - Operations Domains
*
* Controls for: SUBPROCESSOR, TOM, CONTRACT, DATA_SUBJECT, SECURITY, GOVERNANCE
*/
import { Control } from '../types'
// ==========================================
// SUBPROCESSOR - Unterauftragnehmer Controls
// ==========================================
export const SUBPROCESSOR_CONTROLS: Control[] = [
{
id: 'VND-SUB-01',
domain: 'SUBPROCESSOR',
title: {
de: 'Genehmigungspflicht für Unterauftragnehmer',
en: 'Approval requirement for sub-processors',
},
description: {
de: 'Einsatz von Unterauftragnehmern nur mit Genehmigung',
en: 'Use of sub-processors only with approval',
},
passCriteria: {
de: 'Genehmigungserfordernis (spezifisch oder allgemein mit Widerspruchsrecht) vereinbart',
en: 'Approval requirement (specific or general with objection right) agreed',
},
requirements: ['Art. 28 Abs. 2, 4 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-SUB-02',
domain: 'SUBPROCESSOR',
title: {
de: 'Aktuelle Unterauftragnehmer-Liste',
en: 'Current sub-processor list',
},
description: {
de: 'Vollständige und aktuelle Liste aller Unterauftragnehmer',
en: 'Complete and current list of all sub-processors',
},
passCriteria: {
de: 'Liste liegt vor mit Name, Sitz, Verarbeitungszweck',
en: 'List available with name, location, processing purpose',
},
requirements: ['Art. 28 Abs. 2 DSGVO'],
isRequired: true,
defaultFrequency: 'QUARTERLY',
},
{
id: 'VND-SUB-03',
domain: 'SUBPROCESSOR',
title: {
de: 'Informationspflicht bei Änderungen',
en: 'Notification obligation for changes',
},
description: {
de: 'Information über neue oder geänderte Unterauftragnehmer',
en: 'Information about new or changed sub-processors',
},
passCriteria: {
de: 'Vorabinformation vereinbart, ausreichende Frist für Widerspruch',
en: 'Advance notification agreed, sufficient time for objection',
},
requirements: ['Art. 28 Abs. 2 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-SUB-04',
domain: 'SUBPROCESSOR',
title: {
de: 'Weitergabe der Datenschutzpflichten',
en: 'Transfer of data protection obligations',
},
description: {
de: 'Datenschutzpflichten werden an Unterauftragnehmer weitergegeben',
en: 'Data protection obligations are transferred to sub-processors',
},
passCriteria: {
de: 'Vertraglich vereinbart, dass Unterauftragnehmer gleichen Pflichten unterliegen',
en: 'Contractually agreed that sub-processors are subject to same obligations',
},
requirements: ['Art. 28 Abs. 4 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-SUB-05',
domain: 'SUBPROCESSOR',
title: {
de: 'Haftung für Unterauftragnehmer',
en: 'Liability for sub-processors',
},
description: {
de: 'Klare Haftungsregelung für Unterauftragnehmer',
en: 'Clear liability provision for sub-processors',
},
passCriteria: {
de: 'Auftragsverarbeiter haftet für Unterauftragnehmer wie für eigenes Handeln',
en: 'Processor is liable for sub-processors as for own actions',
},
requirements: ['Art. 28 Abs. 4 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
]
// ==========================================
// TOM - Technische/Organisatorische Maßnahmen
// ==========================================
export const TOM_CONTROLS: Control[] = [
{
id: 'VND-TOM-01',
domain: 'TOM',
title: {
de: 'TOM-Dokumentation vorhanden',
en: 'TOM documentation available',
},
description: {
de: 'Vollständige Dokumentation der technischen und organisatorischen Maßnahmen',
en: 'Complete documentation of technical and organizational measures',
},
passCriteria: {
de: 'TOM-Anlage vorhanden, aktuell, spezifisch für die Verarbeitung',
en: 'TOM annex available, current, specific to the processing',
},
requirements: ['Art. 28 Abs. 3 lit. c DSGVO', 'Art. 32 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-TOM-02',
domain: 'TOM',
title: {
de: 'Verschlüsselung',
en: 'Encryption',
},
description: {
de: 'Angemessene Verschlüsselung für Daten in Transit und at Rest',
en: 'Appropriate encryption for data in transit and at rest',
},
passCriteria: {
de: 'TLS 1.2+ für Transit, AES-256 für at Rest',
en: 'TLS 1.2+ for transit, AES-256 for at rest',
},
requirements: ['Art. 32 Abs. 1 lit. a DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-TOM-03',
domain: 'TOM',
title: {
de: 'Zugriffskontrolle',
en: 'Access control',
},
description: {
de: 'Angemessene Zugriffskontrollmechanismen',
en: 'Appropriate access control mechanisms',
},
passCriteria: {
de: 'Rollenbasierte Zugriffskontrolle, Least Privilege, Logging',
en: 'Role-based access control, least privilege, logging',
},
requirements: ['Art. 32 Abs. 1 lit. b DSGVO', 'ISO 27001 A.9'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-TOM-04',
domain: 'TOM',
title: {
de: 'Verfügbarkeit und Wiederherstellung',
en: 'Availability and recovery',
},
description: {
de: 'Maßnahmen zur Sicherstellung der Verfügbarkeit und Wiederherstellung',
en: 'Measures to ensure availability and recovery',
},
passCriteria: {
de: 'Backup-Konzept, DR-Plan, RTO/RPO definiert',
en: 'Backup concept, DR plan, RTO/RPO defined',
},
requirements: ['Art. 32 Abs. 1 lit. b, c DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-TOM-05',
domain: 'TOM',
title: {
de: 'Regelmäßige TOM-Überprüfung',
en: 'Regular TOM review',
},
description: {
de: 'Regelmäßige Überprüfung und Aktualisierung der TOM',
en: 'Regular review and update of TOM',
},
passCriteria: {
de: 'TOM werden mindestens jährlich überprüft und bei Bedarf aktualisiert',
en: 'TOM are reviewed at least annually and updated as needed',
},
requirements: ['Art. 32 Abs. 1 lit. d DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-TOM-06',
domain: 'TOM',
title: {
de: 'Penetrationstest',
en: 'Penetration testing',
},
description: {
de: 'Regelmäßige Penetrationstests der relevanten Systeme',
en: 'Regular penetration testing of relevant systems',
},
passCriteria: {
de: 'Jährlicher Pentest, kritische Findings behoben',
en: 'Annual pentest, critical findings resolved',
},
requirements: ['ISO 27001 A.12.6.1'],
isRequired: false,
defaultFrequency: 'ANNUAL',
},
]
// ==========================================
// CONTRACT - Vertragliche Grundlagen
// ==========================================
export const CONTRACT_CONTROLS: Control[] = [
{
id: 'VND-CON-01',
domain: 'CONTRACT',
title: {
de: 'Weisungsgebundenheit',
en: 'Instruction binding',
},
description: {
de: 'Auftragsverarbeiter ist an Weisungen gebunden',
en: 'Processor is bound by instructions',
},
passCriteria: {
de: 'Weisungsgebundenheit explizit vereinbart, Hinweispflicht bei rechtswidrigen Weisungen',
en: 'Instruction binding explicitly agreed, notification obligation for unlawful instructions',
},
requirements: ['Art. 28 Abs. 3 lit. a DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-CON-02',
domain: 'CONTRACT',
title: {
de: 'Vertraulichkeitsverpflichtung',
en: 'Confidentiality obligation',
},
description: {
de: 'Mitarbeiter sind zur Vertraulichkeit verpflichtet',
en: 'Employees are obligated to confidentiality',
},
passCriteria: {
de: 'Vertraulichkeitsverpflichtung für alle Mitarbeiter mit Datenzugriff',
en: 'Confidentiality obligation for all employees with data access',
},
requirements: ['Art. 28 Abs. 3 lit. b DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-CON-03',
domain: 'CONTRACT',
title: {
de: 'Gegenstand und Dauer der Verarbeitung',
en: 'Subject and duration of processing',
},
description: {
de: 'Klare Definition von Gegenstand und Dauer der Verarbeitung',
en: 'Clear definition of subject and duration of processing',
},
passCriteria: {
de: 'Verarbeitungsgegenstand, Dauer, Art der Daten, Betroffene definiert',
en: 'Processing subject, duration, type of data, data subjects defined',
},
requirements: ['Art. 28 Abs. 3 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-CON-04',
domain: 'CONTRACT',
title: {
de: 'Schriftform/Textform',
en: 'Written/text form',
},
description: {
de: 'AVV in Schriftform oder elektronischem Format',
en: 'DPA in written or electronic format',
},
passCriteria: {
de: 'AVV in Schriftform oder elektronisch mit qualifizierter Signatur',
en: 'DPA in written form or electronically with qualified signature',
},
requirements: ['Art. 28 Abs. 9 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
]
// ==========================================
// DATA_SUBJECT - Betroffenenrechte
// ==========================================
export const DATA_SUBJECT_CONTROLS: Control[] = [
{
id: 'VND-DSR-01',
domain: 'DATA_SUBJECT',
title: {
de: 'Unterstützung bei Betroffenenrechten',
en: 'Support for data subject rights',
},
description: {
de: 'Vendor unterstützt bei der Erfüllung von Betroffenenrechten',
en: 'Vendor supports fulfillment of data subject rights',
},
passCriteria: {
de: 'Unterstützungspflicht vereinbart, Prozess zur Weiterleitung definiert',
en: 'Support obligation agreed, process for forwarding defined',
},
requirements: ['Art. 28 Abs. 3 lit. e DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-DSR-02',
domain: 'DATA_SUBJECT',
title: {
de: 'Reaktionszeit für Anfragen',
en: 'Response time for requests',
},
description: {
de: 'Definierte Reaktionszeit für Betroffenenanfragen',
en: 'Defined response time for data subject requests',
},
passCriteria: {
de: 'Reaktionszeit max. 5 Werktage, um Frist von 1 Monat einhalten zu können',
en: 'Response time max. 5 business days to meet 1 month deadline',
},
requirements: ['Art. 12 Abs. 3 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
]
// ==========================================
// SECURITY - Sicherheit
// ==========================================
export const SECURITY_CONTROLS: Control[] = [
{
id: 'VND-SEC-01',
domain: 'SECURITY',
title: {
de: 'Sicherheitsbewertung',
en: 'Security assessment',
},
description: {
de: 'Regelmäßige Sicherheitsbewertung des Vendors',
en: 'Regular security assessment of the vendor',
},
passCriteria: {
de: 'Sicherheitsfragebogen ausgefüllt, keine kritischen Lücken',
en: 'Security questionnaire completed, no critical gaps',
},
requirements: ['Art. 32 DSGVO', 'ISO 27001 A.15.2.1'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-SEC-02',
domain: 'SECURITY',
title: {
de: 'Vulnerability Management',
en: 'Vulnerability management',
},
description: {
de: 'Etabliertes Vulnerability Management beim Vendor',
en: 'Established vulnerability management at the vendor',
},
passCriteria: {
de: 'Regelmäßige Schwachstellen-Scans, Patch-Management dokumentiert',
en: 'Regular vulnerability scans, patch management documented',
},
requirements: ['ISO 27001 A.12.6'],
isRequired: false,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-SEC-03',
domain: 'SECURITY',
title: {
de: 'Mitarbeiter-Schulung',
en: 'Employee training',
},
description: {
de: 'Datenschutz-Schulung für Mitarbeiter des Vendors',
en: 'Data protection training for vendor employees',
},
passCriteria: {
de: 'Regelmäßige Schulungen (mind. jährlich), Nachweis verfügbar',
en: 'Regular training (at least annually), proof available',
},
requirements: ['Art. 39 Abs. 1 lit. b DSGVO'],
isRequired: false,
defaultFrequency: 'ANNUAL',
},
]
// ==========================================
// GOVERNANCE - Governance
// ==========================================
export const GOVERNANCE_CONTROLS: Control[] = [
{
id: 'VND-GOV-01',
domain: 'GOVERNANCE',
title: {
de: 'Datenschutzbeauftragter benannt',
en: 'Data protection officer appointed',
},
description: {
de: 'Vendor hat DSB benannt (wenn erforderlich)',
en: 'Vendor has appointed DPO (if required)',
},
passCriteria: {
de: 'DSB benannt und Kontaktdaten verfügbar',
en: 'DPO appointed and contact details available',
},
requirements: ['Art. 37 DSGVO'],
isRequired: false,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-GOV-02',
domain: 'GOVERNANCE',
title: {
de: 'Verzeichnis der Verarbeitungstätigkeiten',
en: 'Records of processing activities',
},
description: {
de: 'Vendor führt eigenes Verarbeitungsverzeichnis',
en: 'Vendor maintains own processing records',
},
passCriteria: {
de: 'Verzeichnis nach Art. 30 Abs. 2 DSGVO vorhanden',
en: 'Records according to Art. 30(2) GDPR available',
},
requirements: ['Art. 30 Abs. 2 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-GOV-03',
domain: 'GOVERNANCE',
title: {
de: 'Unterstützung bei DSFA',
en: 'Support for DPIA',
},
description: {
de: 'Vendor unterstützt bei Datenschutz-Folgenabschätzung',
en: 'Vendor supports data protection impact assessment',
},
passCriteria: {
de: 'Unterstützungspflicht bei DSFA vertraglich vereinbart',
en: 'Support obligation for DPIA contractually agreed',
},
requirements: ['Art. 28 Abs. 3 lit. f DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
]

View File

@@ -0,0 +1,115 @@
/**
* Controls Library - Helper Functions
*
* Query, filter, and analysis utilities for the controls library.
*/
import { Control, ControlDomain, ReviewFrequency, LocalizedText } from '../types'
import { CONTROLS_LIBRARY } from './controls-all'
/**
* Get all controls
*/
export function getAllControls(): Control[] {
return CONTROLS_LIBRARY
}
/**
* Get controls by domain
*/
export function getControlsByDomain(domain: ControlDomain): Control[] {
return CONTROLS_LIBRARY.filter((c) => c.domain === domain)
}
/**
* Get control by ID
*/
export function getControlById(id: string): Control | undefined {
return CONTROLS_LIBRARY.find((c) => c.id === id)
}
/**
* Get required controls
*/
export function getRequiredControls(): Control[] {
return CONTROLS_LIBRARY.filter((c) => c.isRequired)
}
/**
* Get controls by frequency
*/
export function getControlsByFrequency(frequency: ReviewFrequency): Control[] {
return CONTROLS_LIBRARY.filter((c) => c.defaultFrequency === frequency)
}
/**
* Get controls applicable to vendors
*/
export function getVendorControls(): Control[] {
return CONTROLS_LIBRARY.filter((c) =>
['TRANSFER', 'AUDIT', 'DELETION', 'INCIDENT', 'SUBPROCESSOR', 'TOM', 'CONTRACT', 'DATA_SUBJECT', 'SECURITY', 'GOVERNANCE'].includes(c.domain)
)
}
/**
* Get controls applicable to processing activities
*/
export function getProcessingActivityControls(): Control[] {
return CONTROLS_LIBRARY.filter((c) =>
['TOM', 'DATA_SUBJECT', 'GOVERNANCE', 'SECURITY'].includes(c.domain)
)
}
/**
* Group controls by domain
*/
export function getControlsGroupedByDomain(): Map<ControlDomain, Control[]> {
const grouped = new Map<ControlDomain, Control[]>()
for (const control of CONTROLS_LIBRARY) {
const existing = grouped.get(control.domain) || []
grouped.set(control.domain, [...existing, control])
}
return grouped
}
/**
* Get domain metadata
*/
export function getControlDomainMeta(domain: ControlDomain): LocalizedText {
const meta: Record<ControlDomain, LocalizedText> = {
TRANSFER: { de: 'Drittlandtransfer', en: 'Third Country Transfer' },
AUDIT: { de: 'Audit & Prüfung', en: 'Audit & Review' },
DELETION: { de: 'Löschung', en: 'Deletion' },
INCIDENT: { de: 'Incident Response', en: 'Incident Response' },
SUBPROCESSOR: { de: 'Unterauftragnehmer', en: 'Sub-Processors' },
TOM: { de: 'Technische/Org. Maßnahmen', en: 'Technical/Org. Measures' },
CONTRACT: { de: 'Vertragliche Grundlagen', en: 'Contractual Basics' },
DATA_SUBJECT: { de: 'Betroffenenrechte', en: 'Data Subject Rights' },
SECURITY: { de: 'Sicherheit', en: 'Security' },
GOVERNANCE: { de: 'Governance', en: 'Governance' },
}
return meta[domain]
}
/**
* Calculate control coverage
*/
export function calculateControlCoverage(
controlIds: string[],
domain?: ControlDomain
): { covered: number; total: number; percentage: number } {
const targetControls = domain
? getControlsByDomain(domain)
: getRequiredControls()
const covered = targetControls.filter((c) => controlIds.includes(c.id)).length
return {
covered,
total: targetControls.length,
percentage: targetControls.length > 0 ? Math.round((covered / targetControls.length) * 100) : 0,
}
}

View File

@@ -1,943 +1,40 @@
/**
* Controls Library
*
* Standard controls for vendor and processing activity compliance
* Standard controls for vendor and processing activity compliance.
* Barrel file that re-exports the merged array, domain arrays, and helpers.
*/
import { Control, ControlDomain, ReviewFrequency, LocalizedText } from '../types'
// Merged controls array
export { CONTROLS_LIBRARY } from './controls-all'
// ==========================================
// CONTROL DEFINITIONS
// ==========================================
// Domain-specific arrays
export {
TRANSFER_CONTROLS,
AUDIT_CONTROLS,
DELETION_CONTROLS,
INCIDENT_CONTROLS,
} from './controls-data-compliance'
export const CONTROLS_LIBRARY: Control[] = [
// ==========================================
// TRANSFER - Drittlandtransfer Controls
// ==========================================
{
id: 'VND-TRF-01',
domain: 'TRANSFER',
title: {
de: 'Drittlandtransfer nur mit Rechtsgrundlage',
en: 'Third country transfer with legal basis',
},
description: {
de: 'Drittlandtransfers erfolgen nur auf Basis von SCC, BCR oder Angemessenheitsbeschluss',
en: 'Third country transfers only based on SCC, BCR or adequacy decision',
},
passCriteria: {
de: 'SCC oder BCR vertraglich vereinbart ODER Angemessenheitsbeschluss vorhanden',
en: 'SCC or BCR contractually agreed OR adequacy decision exists',
},
requirements: ['Art. 44-49 DSGVO', 'ISO 27001 A.15.1.2'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-TRF-02',
domain: 'TRANSFER',
title: {
de: 'Aktuelle Standardvertragsklauseln',
en: 'Current Standard Contractual Clauses',
},
description: {
de: 'Bei SCC-Nutzung: Verwendung der aktuellen EU-Kommission-Klauseln (2021)',
en: 'When using SCC: Current EU Commission clauses (2021) are used',
},
passCriteria: {
de: 'SCC 2021 (Durchführungsbeschluss (EU) 2021/914) verwendet',
en: 'SCC 2021 (Implementing Decision (EU) 2021/914) used',
},
requirements: ['Art. 46 Abs. 2 lit. c DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-TRF-03',
domain: 'TRANSFER',
title: {
de: 'Transfer Impact Assessment (TIA)',
en: 'Transfer Impact Assessment (TIA)',
},
description: {
de: 'Bei Transfers in Drittländer ohne Angemessenheitsbeschluss ist TIA durchzuführen',
en: 'TIA required for transfers to third countries without adequacy decision',
},
passCriteria: {
de: 'TIA dokumentiert und bewertet Risiken als akzeptabel',
en: 'TIA documented and risks assessed as acceptable',
},
requirements: ['Schrems II Urteil', 'EDSA Empfehlungen 01/2020'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-TRF-04',
domain: 'TRANSFER',
title: {
de: 'Zusätzliche Schutzmaßnahmen',
en: 'Supplementary Measures',
},
description: {
de: 'Bei Bedarf sind zusätzliche technische/organisatorische Maßnahmen implementiert',
en: 'Supplementary technical/organizational measures implemented where needed',
},
passCriteria: {
de: 'Ergänzende Maßnahmen dokumentiert (Verschlüsselung, Pseudonymisierung, etc.)',
en: 'Supplementary measures documented (encryption, pseudonymization, etc.)',
},
requirements: ['EDSA Empfehlungen 01/2020'],
isRequired: false,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-TRF-05',
domain: 'TRANSFER',
title: {
de: 'Überwachung Angemessenheitsbeschlüsse',
en: 'Monitoring Adequacy Decisions',
},
description: {
de: 'Änderungen bei Angemessenheitsbeschlüssen werden überwacht',
en: 'Changes to adequacy decisions are monitored',
},
passCriteria: {
de: 'Prozess zur Überwachung und Reaktion auf Änderungen etabliert',
en: 'Process for monitoring and responding to changes established',
},
requirements: ['Art. 45 DSGVO'],
isRequired: false,
defaultFrequency: 'QUARTERLY',
},
export {
SUBPROCESSOR_CONTROLS,
TOM_CONTROLS,
CONTRACT_CONTROLS,
DATA_SUBJECT_CONTROLS,
SECURITY_CONTROLS,
GOVERNANCE_CONTROLS,
} from './controls-data-operations'
// ==========================================
// AUDIT - Auditrechte Controls
// ==========================================
{
id: 'VND-AUD-01',
domain: 'AUDIT',
title: {
de: 'Auditrecht vertraglich vereinbart',
en: 'Audit right contractually agreed',
},
description: {
de: 'Vertrag enthält wirksames Auditrecht ohne unangemessene Einschränkungen',
en: 'Contract contains effective audit right without unreasonable restrictions',
},
passCriteria: {
de: 'Auditrecht im AVV enthalten, max. 30 Tage Vorlaufzeit, keine Ausschlussklausel',
en: 'Audit right in DPA, max 30 days notice, no exclusion clause',
},
requirements: ['Art. 28 Abs. 3 lit. h DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-AUD-02',
domain: 'AUDIT',
title: {
de: 'Vor-Ort-Inspektionen möglich',
en: 'On-site inspections possible',
},
description: {
de: 'Vertrag erlaubt Vor-Ort-Inspektionen bei dem Auftragsverarbeiter',
en: 'Contract allows on-site inspections at the processor',
},
passCriteria: {
de: 'Vor-Ort-Audit explizit erlaubt, Zugang zu relevanten Bereichen',
en: 'On-site audit explicitly allowed, access to relevant areas',
},
requirements: ['Art. 28 Abs. 3 lit. h DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-AUD-03',
domain: 'AUDIT',
title: {
de: 'Aktuelle Zertifizierungen',
en: 'Current Certifications',
},
description: {
de: 'Relevante Sicherheitszertifizierungen sind aktuell und gültig',
en: 'Relevant security certifications are current and valid',
},
passCriteria: {
de: 'ISO 27001, SOC 2 oder vergleichbar, nicht abgelaufen',
en: 'ISO 27001, SOC 2 or equivalent, not expired',
},
requirements: ['Art. 32 DSGVO', 'ISO 27001 A.15.1.1'],
isRequired: false,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-AUD-04',
domain: 'AUDIT',
title: {
de: 'Letzte Prüfung durchgeführt',
en: 'Last review conducted',
},
description: {
de: 'Vendor wurde innerhalb des Review-Zyklus geprüft',
en: 'Vendor was reviewed within the review cycle',
},
passCriteria: {
de: 'Dokumentierte Prüfung innerhalb des festgelegten Intervalls',
en: 'Documented review within the defined interval',
},
requirements: ['Art. 28 Abs. 3 lit. h DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-AUD-05',
domain: 'AUDIT',
title: {
de: 'Prüfberichte verfügbar',
en: 'Audit reports available',
},
description: {
de: 'Aktuelle Prüfberichte (SOC 2, Penetrationstest, etc.) liegen vor',
en: 'Current audit reports (SOC 2, penetration test, etc.) are available',
},
passCriteria: {
de: 'Prüfberichte nicht älter als 12 Monate',
en: 'Audit reports not older than 12 months',
},
requirements: ['ISO 27001 A.18.2.1'],
isRequired: false,
defaultFrequency: 'ANNUAL',
},
// ==========================================
// DELETION - Löschung Controls
// ==========================================
{
id: 'VND-DEL-01',
domain: 'DELETION',
title: {
de: 'Löschung/Rückgabe nach Vertragsende',
en: 'Deletion/return after contract end',
},
description: {
de: 'Klare Regelung zur Löschung oder Rückgabe aller Daten nach Vertragsende',
en: 'Clear provision for deletion or return of all data after contract end',
},
passCriteria: {
de: 'Löschfrist max. 30 Tage, Löschbestätigung vorgesehen',
en: 'Deletion within max 30 days, deletion confirmation provided',
},
requirements: ['Art. 28 Abs. 3 lit. g DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-DEL-02',
domain: 'DELETION',
title: {
de: 'Löschbestätigung',
en: 'Deletion confirmation',
},
description: {
de: 'Schriftliche Bestätigung der vollständigen Datenlöschung',
en: 'Written confirmation of complete data deletion',
},
passCriteria: {
de: 'Löschbestätigung vertraglich vereinbart und einforderbar',
en: 'Deletion confirmation contractually agreed and enforceable',
},
requirements: ['Art. 28 Abs. 3 lit. g DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-DEL-03',
domain: 'DELETION',
title: {
de: 'Löschung bei Unterauftragnehmern',
en: 'Deletion at sub-processors',
},
description: {
de: 'Löschpflicht erstreckt sich auf alle Unterauftragnehmer',
en: 'Deletion obligation extends to all sub-processors',
},
passCriteria: {
de: 'Weitergabe der Löschpflicht an Unterauftragnehmer vertraglich vereinbart',
en: 'Transfer of deletion obligation to sub-processors contractually agreed',
},
requirements: ['Art. 28 Abs. 3 lit. g, d DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-DEL-04',
domain: 'DELETION',
title: {
de: 'Backup-Löschung',
en: 'Backup deletion',
},
description: {
de: 'Daten werden auch aus Backups gelöscht',
en: 'Data is also deleted from backups',
},
passCriteria: {
de: 'Backup-Löschung geregelt, max. Aufbewahrungsfrist für Backups definiert',
en: 'Backup deletion regulated, max retention period for backups defined',
},
requirements: ['Art. 28 Abs. 3 lit. g DSGVO'],
isRequired: false,
defaultFrequency: 'ANNUAL',
},
// ==========================================
// INCIDENT - Incident Response Controls
// ==========================================
{
id: 'VND-INC-01',
domain: 'INCIDENT',
title: {
de: 'Meldepflicht bei Datenpannen',
en: 'Data breach notification obligation',
},
description: {
de: 'Unverzügliche Meldung von Datenschutzverletzungen',
en: 'Immediate notification of data protection violations',
},
passCriteria: {
de: 'Meldepflicht vereinbart, Frist max. 24-48h, Mindestinhalte definiert',
en: 'Notification obligation agreed, deadline max 24-48h, minimum content defined',
},
requirements: ['Art. 33 Abs. 2 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-INC-02',
domain: 'INCIDENT',
title: {
de: 'Incident Response Plan',
en: 'Incident Response Plan',
},
description: {
de: 'Vendor hat dokumentierten Incident Response Plan',
en: 'Vendor has documented incident response plan',
},
passCriteria: {
de: 'Incident Response Plan liegt vor und wurde getestet',
en: 'Incident response plan exists and has been tested',
},
requirements: ['ISO 27001 A.16.1'],
isRequired: false,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-INC-03',
domain: 'INCIDENT',
title: {
de: 'Kontaktstelle für Incidents',
en: 'Contact point for incidents',
},
description: {
de: 'Definierte Kontaktstelle für Datenschutzvorfälle',
en: 'Defined contact point for data protection incidents',
},
passCriteria: {
de: 'Kontaktdaten für Incident-Meldungen bekannt und aktuell',
en: 'Contact details for incident reporting known and current',
},
requirements: ['Art. 33 Abs. 2 DSGVO'],
isRequired: true,
defaultFrequency: 'QUARTERLY',
},
{
id: 'VND-INC-04',
domain: 'INCIDENT',
title: {
de: 'Unterstützung bei Incident-Dokumentation',
en: 'Support with incident documentation',
},
description: {
de: 'Vendor unterstützt bei der Dokumentation von Vorfällen',
en: 'Vendor supports documentation of incidents',
},
passCriteria: {
de: 'Unterstützungspflicht bei Dokumentation vertraglich vereinbart',
en: 'Support obligation for documentation contractually agreed',
},
requirements: ['Art. 33 Abs. 5 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
// ==========================================
// SUBPROCESSOR - Unterauftragnehmer Controls
// ==========================================
{
id: 'VND-SUB-01',
domain: 'SUBPROCESSOR',
title: {
de: 'Genehmigungspflicht für Unterauftragnehmer',
en: 'Approval requirement for sub-processors',
},
description: {
de: 'Einsatz von Unterauftragnehmern nur mit Genehmigung',
en: 'Use of sub-processors only with approval',
},
passCriteria: {
de: 'Genehmigungserfordernis (spezifisch oder allgemein mit Widerspruchsrecht) vereinbart',
en: 'Approval requirement (specific or general with objection right) agreed',
},
requirements: ['Art. 28 Abs. 2, 4 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-SUB-02',
domain: 'SUBPROCESSOR',
title: {
de: 'Aktuelle Unterauftragnehmer-Liste',
en: 'Current sub-processor list',
},
description: {
de: 'Vollständige und aktuelle Liste aller Unterauftragnehmer',
en: 'Complete and current list of all sub-processors',
},
passCriteria: {
de: 'Liste liegt vor mit Name, Sitz, Verarbeitungszweck',
en: 'List available with name, location, processing purpose',
},
requirements: ['Art. 28 Abs. 2 DSGVO'],
isRequired: true,
defaultFrequency: 'QUARTERLY',
},
{
id: 'VND-SUB-03',
domain: 'SUBPROCESSOR',
title: {
de: 'Informationspflicht bei Änderungen',
en: 'Notification obligation for changes',
},
description: {
de: 'Information über neue oder geänderte Unterauftragnehmer',
en: 'Information about new or changed sub-processors',
},
passCriteria: {
de: 'Vorabinformation vereinbart, ausreichende Frist für Widerspruch',
en: 'Advance notification agreed, sufficient time for objection',
},
requirements: ['Art. 28 Abs. 2 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-SUB-04',
domain: 'SUBPROCESSOR',
title: {
de: 'Weitergabe der Datenschutzpflichten',
en: 'Transfer of data protection obligations',
},
description: {
de: 'Datenschutzpflichten werden an Unterauftragnehmer weitergegeben',
en: 'Data protection obligations are transferred to sub-processors',
},
passCriteria: {
de: 'Vertraglich vereinbart, dass Unterauftragnehmer gleichen Pflichten unterliegen',
en: 'Contractually agreed that sub-processors are subject to same obligations',
},
requirements: ['Art. 28 Abs. 4 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-SUB-05',
domain: 'SUBPROCESSOR',
title: {
de: 'Haftung für Unterauftragnehmer',
en: 'Liability for sub-processors',
},
description: {
de: 'Klare Haftungsregelung für Unterauftragnehmer',
en: 'Clear liability provision for sub-processors',
},
passCriteria: {
de: 'Auftragsverarbeiter haftet für Unterauftragnehmer wie für eigenes Handeln',
en: 'Processor is liable for sub-processors as for own actions',
},
requirements: ['Art. 28 Abs. 4 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
// ==========================================
// TOM - Technische/Organisatorische Maßnahmen
// ==========================================
{
id: 'VND-TOM-01',
domain: 'TOM',
title: {
de: 'TOM-Dokumentation vorhanden',
en: 'TOM documentation available',
},
description: {
de: 'Vollständige Dokumentation der technischen und organisatorischen Maßnahmen',
en: 'Complete documentation of technical and organizational measures',
},
passCriteria: {
de: 'TOM-Anlage vorhanden, aktuell, spezifisch für die Verarbeitung',
en: 'TOM annex available, current, specific to the processing',
},
requirements: ['Art. 28 Abs. 3 lit. c DSGVO', 'Art. 32 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-TOM-02',
domain: 'TOM',
title: {
de: 'Verschlüsselung',
en: 'Encryption',
},
description: {
de: 'Angemessene Verschlüsselung für Daten in Transit und at Rest',
en: 'Appropriate encryption for data in transit and at rest',
},
passCriteria: {
de: 'TLS 1.2+ für Transit, AES-256 für at Rest',
en: 'TLS 1.2+ for transit, AES-256 for at rest',
},
requirements: ['Art. 32 Abs. 1 lit. a DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-TOM-03',
domain: 'TOM',
title: {
de: 'Zugriffskontrolle',
en: 'Access control',
},
description: {
de: 'Angemessene Zugriffskontrollmechanismen',
en: 'Appropriate access control mechanisms',
},
passCriteria: {
de: 'Rollenbasierte Zugriffskontrolle, Least Privilege, Logging',
en: 'Role-based access control, least privilege, logging',
},
requirements: ['Art. 32 Abs. 1 lit. b DSGVO', 'ISO 27001 A.9'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-TOM-04',
domain: 'TOM',
title: {
de: 'Verfügbarkeit und Wiederherstellung',
en: 'Availability and recovery',
},
description: {
de: 'Maßnahmen zur Sicherstellung der Verfügbarkeit und Wiederherstellung',
en: 'Measures to ensure availability and recovery',
},
passCriteria: {
de: 'Backup-Konzept, DR-Plan, RTO/RPO definiert',
en: 'Backup concept, DR plan, RTO/RPO defined',
},
requirements: ['Art. 32 Abs. 1 lit. b, c DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-TOM-05',
domain: 'TOM',
title: {
de: 'Regelmäßige TOM-Überprüfung',
en: 'Regular TOM review',
},
description: {
de: 'Regelmäßige Überprüfung und Aktualisierung der TOM',
en: 'Regular review and update of TOM',
},
passCriteria: {
de: 'TOM werden mindestens jährlich überprüft und bei Bedarf aktualisiert',
en: 'TOM are reviewed at least annually and updated as needed',
},
requirements: ['Art. 32 Abs. 1 lit. d DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-TOM-06',
domain: 'TOM',
title: {
de: 'Penetrationstest',
en: 'Penetration testing',
},
description: {
de: 'Regelmäßige Penetrationstests der relevanten Systeme',
en: 'Regular penetration testing of relevant systems',
},
passCriteria: {
de: 'Jährlicher Pentest, kritische Findings behoben',
en: 'Annual pentest, critical findings resolved',
},
requirements: ['ISO 27001 A.12.6.1'],
isRequired: false,
defaultFrequency: 'ANNUAL',
},
// ==========================================
// CONTRACT - Vertragliche Grundlagen
// ==========================================
{
id: 'VND-CON-01',
domain: 'CONTRACT',
title: {
de: 'Weisungsgebundenheit',
en: 'Instruction binding',
},
description: {
de: 'Auftragsverarbeiter ist an Weisungen gebunden',
en: 'Processor is bound by instructions',
},
passCriteria: {
de: 'Weisungsgebundenheit explizit vereinbart, Hinweispflicht bei rechtswidrigen Weisungen',
en: 'Instruction binding explicitly agreed, notification obligation for unlawful instructions',
},
requirements: ['Art. 28 Abs. 3 lit. a DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-CON-02',
domain: 'CONTRACT',
title: {
de: 'Vertraulichkeitsverpflichtung',
en: 'Confidentiality obligation',
},
description: {
de: 'Mitarbeiter sind zur Vertraulichkeit verpflichtet',
en: 'Employees are obligated to confidentiality',
},
passCriteria: {
de: 'Vertraulichkeitsverpflichtung für alle Mitarbeiter mit Datenzugriff',
en: 'Confidentiality obligation for all employees with data access',
},
requirements: ['Art. 28 Abs. 3 lit. b DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-CON-03',
domain: 'CONTRACT',
title: {
de: 'Gegenstand und Dauer der Verarbeitung',
en: 'Subject and duration of processing',
},
description: {
de: 'Klare Definition von Gegenstand und Dauer der Verarbeitung',
en: 'Clear definition of subject and duration of processing',
},
passCriteria: {
de: 'Verarbeitungsgegenstand, Dauer, Art der Daten, Betroffene definiert',
en: 'Processing subject, duration, type of data, data subjects defined',
},
requirements: ['Art. 28 Abs. 3 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-CON-04',
domain: 'CONTRACT',
title: {
de: 'Schriftform/Textform',
en: 'Written/text form',
},
description: {
de: 'AVV in Schriftform oder elektronischem Format',
en: 'DPA in written or electronic format',
},
passCriteria: {
de: 'AVV in Schriftform oder elektronisch mit qualifizierter Signatur',
en: 'DPA in written form or electronically with qualified signature',
},
requirements: ['Art. 28 Abs. 9 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
// ==========================================
// DATA_SUBJECT - Betroffenenrechte
// ==========================================
{
id: 'VND-DSR-01',
domain: 'DATA_SUBJECT',
title: {
de: 'Unterstützung bei Betroffenenrechten',
en: 'Support for data subject rights',
},
description: {
de: 'Vendor unterstützt bei der Erfüllung von Betroffenenrechten',
en: 'Vendor supports fulfillment of data subject rights',
},
passCriteria: {
de: 'Unterstützungspflicht vereinbart, Prozess zur Weiterleitung definiert',
en: 'Support obligation agreed, process for forwarding defined',
},
requirements: ['Art. 28 Abs. 3 lit. e DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-DSR-02',
domain: 'DATA_SUBJECT',
title: {
de: 'Reaktionszeit für Anfragen',
en: 'Response time for requests',
},
description: {
de: 'Definierte Reaktionszeit für Betroffenenanfragen',
en: 'Defined response time for data subject requests',
},
passCriteria: {
de: 'Reaktionszeit max. 5 Werktage, um Frist von 1 Monat einhalten zu können',
en: 'Response time max. 5 business days to meet 1 month deadline',
},
requirements: ['Art. 12 Abs. 3 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
// ==========================================
// SECURITY - Sicherheit
// ==========================================
{
id: 'VND-SEC-01',
domain: 'SECURITY',
title: {
de: 'Sicherheitsbewertung',
en: 'Security assessment',
},
description: {
de: 'Regelmäßige Sicherheitsbewertung des Vendors',
en: 'Regular security assessment of the vendor',
},
passCriteria: {
de: 'Sicherheitsfragebogen ausgefüllt, keine kritischen Lücken',
en: 'Security questionnaire completed, no critical gaps',
},
requirements: ['Art. 32 DSGVO', 'ISO 27001 A.15.2.1'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-SEC-02',
domain: 'SECURITY',
title: {
de: 'Vulnerability Management',
en: 'Vulnerability management',
},
description: {
de: 'Etabliertes Vulnerability Management beim Vendor',
en: 'Established vulnerability management at the vendor',
},
passCriteria: {
de: 'Regelmäßige Schwachstellen-Scans, Patch-Management dokumentiert',
en: 'Regular vulnerability scans, patch management documented',
},
requirements: ['ISO 27001 A.12.6'],
isRequired: false,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-SEC-03',
domain: 'SECURITY',
title: {
de: 'Mitarbeiter-Schulung',
en: 'Employee training',
},
description: {
de: 'Datenschutz-Schulung für Mitarbeiter des Vendors',
en: 'Data protection training for vendor employees',
},
passCriteria: {
de: 'Regelmäßige Schulungen (mind. jährlich), Nachweis verfügbar',
en: 'Regular training (at least annually), proof available',
},
requirements: ['Art. 39 Abs. 1 lit. b DSGVO'],
isRequired: false,
defaultFrequency: 'ANNUAL',
},
// ==========================================
// GOVERNANCE - Governance
// ==========================================
{
id: 'VND-GOV-01',
domain: 'GOVERNANCE',
title: {
de: 'Datenschutzbeauftragter benannt',
en: 'Data protection officer appointed',
},
description: {
de: 'Vendor hat DSB benannt (wenn erforderlich)',
en: 'Vendor has appointed DPO (if required)',
},
passCriteria: {
de: 'DSB benannt und Kontaktdaten verfügbar',
en: 'DPO appointed and contact details available',
},
requirements: ['Art. 37 DSGVO'],
isRequired: false,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-GOV-02',
domain: 'GOVERNANCE',
title: {
de: 'Verzeichnis der Verarbeitungstätigkeiten',
en: 'Records of processing activities',
},
description: {
de: 'Vendor führt eigenes Verarbeitungsverzeichnis',
en: 'Vendor maintains own processing records',
},
passCriteria: {
de: 'Verzeichnis nach Art. 30 Abs. 2 DSGVO vorhanden',
en: 'Records according to Art. 30(2) GDPR available',
},
requirements: ['Art. 30 Abs. 2 DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
{
id: 'VND-GOV-03',
domain: 'GOVERNANCE',
title: {
de: 'Unterstützung bei DSFA',
en: 'Support for DPIA',
},
description: {
de: 'Vendor unterstützt bei Datenschutz-Folgenabschätzung',
en: 'Vendor supports data protection impact assessment',
},
passCriteria: {
de: 'Unterstützungspflicht bei DSFA vertraglich vereinbart',
en: 'Support obligation for DPIA contractually agreed',
},
requirements: ['Art. 28 Abs. 3 lit. f DSGVO'],
isRequired: true,
defaultFrequency: 'ANNUAL',
},
]
// ==========================================
// HELPER FUNCTIONS
// ==========================================
/**
* Get all controls
*/
export function getAllControls(): Control[] {
return CONTROLS_LIBRARY
}
/**
* Get controls by domain
*/
export function getControlsByDomain(domain: ControlDomain): Control[] {
return CONTROLS_LIBRARY.filter((c) => c.domain === domain)
}
/**
* Get control by ID
*/
export function getControlById(id: string): Control | undefined {
return CONTROLS_LIBRARY.find((c) => c.id === id)
}
/**
* Get required controls
*/
export function getRequiredControls(): Control[] {
return CONTROLS_LIBRARY.filter((c) => c.isRequired)
}
/**
* Get controls by frequency
*/
export function getControlsByFrequency(frequency: ReviewFrequency): Control[] {
return CONTROLS_LIBRARY.filter((c) => c.defaultFrequency === frequency)
}
/**
* Get controls applicable to vendors
*/
export function getVendorControls(): Control[] {
return CONTROLS_LIBRARY.filter((c) =>
['TRANSFER', 'AUDIT', 'DELETION', 'INCIDENT', 'SUBPROCESSOR', 'TOM', 'CONTRACT', 'DATA_SUBJECT', 'SECURITY', 'GOVERNANCE'].includes(c.domain)
)
}
/**
* Get controls applicable to processing activities
*/
export function getProcessingActivityControls(): Control[] {
return CONTROLS_LIBRARY.filter((c) =>
['TOM', 'DATA_SUBJECT', 'GOVERNANCE', 'SECURITY'].includes(c.domain)
)
}
/**
* Group controls by domain
*/
export function getControlsGroupedByDomain(): Map<ControlDomain, Control[]> {
const grouped = new Map<ControlDomain, Control[]>()
for (const control of CONTROLS_LIBRARY) {
const existing = grouped.get(control.domain) || []
grouped.set(control.domain, [...existing, control])
}
return grouped
}
/**
* Get domain metadata
*/
export function getControlDomainMeta(domain: ControlDomain): LocalizedText {
const meta: Record<ControlDomain, LocalizedText> = {
TRANSFER: { de: 'Drittlandtransfer', en: 'Third Country Transfer' },
AUDIT: { de: 'Audit & Prüfung', en: 'Audit & Review' },
DELETION: { de: 'Löschung', en: 'Deletion' },
INCIDENT: { de: 'Incident Response', en: 'Incident Response' },
SUBPROCESSOR: { de: 'Unterauftragnehmer', en: 'Sub-Processors' },
TOM: { de: 'Technische/Org. Maßnahmen', en: 'Technical/Org. Measures' },
CONTRACT: { de: 'Vertragliche Grundlagen', en: 'Contractual Basics' },
DATA_SUBJECT: { de: 'Betroffenenrechte', en: 'Data Subject Rights' },
SECURITY: { de: 'Sicherheit', en: 'Security' },
GOVERNANCE: { de: 'Governance', en: 'Governance' },
}
return meta[domain]
}
/**
* Calculate control coverage
*/
export function calculateControlCoverage(
controlIds: string[],
domain?: ControlDomain
): { covered: number; total: number; percentage: number } {
const targetControls = domain
? getControlsByDomain(domain)
: getRequiredControls()
const covered = targetControls.filter((c) => controlIds.includes(c.id)).length
return {
covered,
total: targetControls.length,
percentage: targetControls.length > 0 ? Math.round((covered / targetControls.length) * 100) : 0,
}
}
// Helper functions
export {
getAllControls,
getControlsByDomain,
getControlById,
getRequiredControls,
getControlsByFrequency,
getVendorControls,
getProcessingActivityControls,
getControlsGroupedByDomain,
getControlDomainMeta,
calculateControlCoverage,
} from './controls-helpers'

View File

@@ -0,0 +1,50 @@
/**
* Audit & Export Types
*
* Types for report generation and data export:
* - Report types (VVT, RoPA, vendor audit, etc.)
* - Export formats (PDF, DOCX, XLSX, JSON)
* - Audit report snapshots
*/
// ==========================================
// ENUMS - EXPORT
// ==========================================
export type ReportType =
| 'VVT_EXPORT'
| 'VENDOR_AUDIT'
| 'ROPA'
| 'MANAGEMENT_SUMMARY'
| 'DPIA_INPUT'
export type ExportFormat = 'PDF' | 'DOCX' | 'XLSX' | 'JSON'
// ==========================================
// INTERFACES - AUDIT REPORTS
// ==========================================
export interface ReportScope {
vendorIds?: string[]
processingActivityIds?: string[]
dateRange?: { from: Date; to: Date }
}
export interface AuditReport {
id: string
tenantId: string
type: ReportType
// Scope
scope: ReportScope
// Generierung
format: ExportFormat
storagePath: string
generatedAt: Date
generatedBy: string
// Snapshot-Daten (fuer Revisionssicherheit)
snapshotHash: string // SHA-256 des Inhalts
}

View File

@@ -0,0 +1,58 @@
/**
* Common / Shared Types
*
* Foundational types used across all vendor-compliance domains:
* LocalizedText, Address, Contact, ResponsibleParty, Organization
*/
// ==========================================
// LOCALIZED TEXT
// ==========================================
export interface LocalizedText {
de: string
en: string
}
// ==========================================
// COMMON TYPES
// ==========================================
export interface Address {
street: string
city: string
postalCode: string
country: string // ISO 3166-1 alpha-2
state?: string
}
export interface Contact {
name: string
email: string
phone?: string
department?: string
role?: string
}
export interface ResponsibleParty {
organizationName: string
legalForm?: string
address: Address
contact: Contact
}
// ==========================================
// ORGANISATION / TENANT
// ==========================================
export interface Organization {
id: string
name: string
legalForm: string // GmbH, AG, e.V., etc.
address: Address
country: string // ISO 3166-1 alpha-2
vatId?: string
dpoContact: Contact // Datenschutzbeauftragter
createdAt: Date
updatedAt: Date
}

View File

@@ -0,0 +1,117 @@
/**
* Contract Types
*
* Types for contract document management:
* - Document types (AVV, MSA, SLA, SCC, etc.)
* - Contract review and lifecycle statuses
* - Contract document and version interfaces
*/
import type { Address } from './types-common'
// ==========================================
// ENUMS - CONTRACT
// ==========================================
export type DocumentType =
| 'AVV' // Auftragsverarbeitungsvertrag
| 'MSA' // Master Service Agreement
| 'SLA' // Service Level Agreement
| 'SCC' // Standard Contractual Clauses
| 'NDA' // Non-Disclosure Agreement
| 'TOM_ANNEX' // TOM-Anlage
| 'CERTIFICATION' // Zertifikat
| 'SUB_PROCESSOR_LIST' // Unterauftragsverarbeiter-Liste
| 'OTHER'
export type ContractReviewStatus =
| 'PENDING'
| 'IN_PROGRESS'
| 'COMPLETED'
| 'FAILED'
export type ContractStatus =
| 'DRAFT'
| 'SIGNED'
| 'ACTIVE'
| 'EXPIRED'
| 'TERMINATED'
// ==========================================
// INTERFACES - CONTRACT
// ==========================================
export interface ContractParty {
role: 'CONTROLLER' | 'PROCESSOR' | 'PARTY'
name: string
address?: Address
signatoryName?: string
signatoryRole?: string
}
export interface ContractDocument {
id: string
tenantId: string
vendorId: string
// Dokument
fileName: string
originalName: string
mimeType: string
fileSize: number
storagePath: string // MinIO path
// Klassifikation
documentType: DocumentType
// Versioning
version: string
previousVersionId?: string
// Metadaten (extrahiert)
parties?: ContractParty[]
effectiveDate?: Date
expirationDate?: Date
autoRenewal?: boolean
renewalNoticePeriod?: number // Tage
terminationNoticePeriod?: number // Tage
// Review Status
reviewStatus: ContractReviewStatus
reviewCompletedAt?: Date
complianceScore?: number // 0-100
// Workflow
status: ContractStatus
signedAt?: Date
// Extracted text for search
extractedText?: string
pageCount?: number
createdAt: Date
updatedAt: Date
}
export interface DocumentVersion {
id: string
documentId: string
version: string
storagePath: string
extractedText?: string
pageCount: number
createdAt: Date
}
// ==========================================
// FORM TYPES - CONTRACT
// ==========================================
export interface ContractUploadData {
vendorId: string
documentType: DocumentType
version: string
effectiveDate?: Date
expirationDate?: Date
autoRenewal?: boolean
}

View File

@@ -0,0 +1,93 @@
/**
* Finding Types
*
* Types for compliance findings from contract reviews:
* - Finding classification (type, category, severity)
* - Citation references to source documents
* - Finding workflow (status, resolution)
*/
import type { LocalizedText } from './types-common'
// ==========================================
// ENUMS - FINDINGS
// ==========================================
export type FindingType =
| 'OK' // Anforderung erfuellt
| 'GAP' // Luecke/fehlend
| 'RISK' // Risiko identifiziert
| 'UNKNOWN' // Nicht eindeutig
export type FindingCategory =
| 'AVV_CONTENT' // Art. 28 Abs. 3 Mindestinhalte
| 'SUBPROCESSOR' // Unterauftragnehmer-Regelung
| 'INCIDENT' // Incident-Meldepflichten
| 'AUDIT_RIGHTS' // Audit-/Inspektionsrechte
| 'DELETION' // Loeschung/Rueckgabe
| 'TOM' // Technische/Org. Massnahmen
| 'TRANSFER' // Drittlandtransfer
| 'LIABILITY' // Haftung/Indemnity
| 'SLA' // Verfuegbarkeit
| 'DATA_SUBJECT_RIGHTS' // Betroffenenrechte
| 'CONFIDENTIALITY' // Vertraulichkeit
| 'INSTRUCTION' // Weisungsgebundenheit
| 'TERMINATION' // Vertragsbeendigung
| 'GENERAL' // Allgemein
export type FindingSeverity = 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL'
export type FindingStatus =
| 'OPEN'
| 'IN_PROGRESS'
| 'RESOLVED'
| 'ACCEPTED'
| 'FALSE_POSITIVE'
// ==========================================
// INTERFACES - FINDINGS
// ==========================================
export interface Citation {
documentId: string
versionId?: string
page: number
startChar: number
endChar: number
quotedText: string
quoteHash: string // SHA-256 zur Verifizierung
}
export interface Finding {
id: string
tenantId: string
contractId: string
vendorId: string
// Klassifikation
type: FindingType
category: FindingCategory
severity: FindingSeverity
// Inhalt
title: LocalizedText
description: LocalizedText
recommendation?: LocalizedText
// Citations (Textstellen-Belege)
citations: Citation[]
// Verknuepfung
affectedRequirement?: string // z.B. "Art. 28 Abs. 3 lit. a DSGVO"
triggeredControls: string[] // Control-IDs
// Workflow
status: FindingStatus
assignee?: string
dueDate?: Date
resolution?: string
resolvedAt?: Date
createdAt: Date
updatedAt: Date
}

View File

@@ -0,0 +1,280 @@
/**
* Helper Functions & Constants (Metadata)
*
* Utility functions for risk calculation, formatting, validation.
* Localized metadata constants for enums (labels, articles, etc.).
*/
import type { LocalizedText } from './types-common'
import type {
PersonalDataCategory,
DataSubjectCategory,
LegalBasisType,
TransferMechanismType,
} from './types-processing'
import type {
VendorRole,
VendorStatus,
ServiceCategory,
} from './types-vendor'
import type {
DocumentType,
ContractStatus,
} from './types-contract'
import type {
FindingSeverity,
} from './types-finding'
import type {
RiskLevel,
} from './types-risk'
import type {
ProcessingActivityStatus,
} from './types-processing'
// ==========================================
// HELPER FUNCTIONS
// ==========================================
/**
* Calculate risk level from score
*/
export function getRiskLevelFromScore(score: number): RiskLevel {
if (score <= 4) return 'LOW'
if (score <= 9) return 'MEDIUM'
if (score <= 16) return 'HIGH'
return 'CRITICAL'
}
/**
* Calculate risk score from likelihood and impact
*/
export function calculateRiskScore(likelihood: number, impact: number): number {
return likelihood * impact
}
/**
* Check if data category is special (Art. 9 DSGVO)
*/
export function isSpecialCategory(category: PersonalDataCategory): boolean {
const specialCategories: PersonalDataCategory[] = [
'HEALTH_DATA',
'GENETIC_DATA',
'BIOMETRIC_DATA',
'RACIAL_ETHNIC',
'POLITICAL_OPINIONS',
'RELIGIOUS_BELIEFS',
'TRADE_UNION',
'SEX_LIFE',
'CRIMINAL_DATA',
]
return specialCategories.includes(category)
}
/**
* Check if country has adequacy decision
*/
export function hasAdequacyDecision(countryCode: string): boolean {
const adequateCountries = [
'AD', 'AR', 'CA', 'FO', 'GG', 'IL', 'IM', 'JP', 'JE', 'NZ', 'KR', 'CH', 'GB', 'UY',
// EU/EEA countries
'AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR', 'DE', 'GR', 'HU',
'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE',
'IS', 'LI', 'NO',
]
return adequateCountries.includes(countryCode.toUpperCase())
}
/**
* Generate VVT ID
*/
export function generateVVTId(existingIds: string[]): string {
const year = new Date().getFullYear()
const prefix = `VVT-${year}-`
const existingNumbers = existingIds
.filter(id => id.startsWith(prefix))
.map(id => parseInt(id.replace(prefix, ''), 10))
.filter(n => !isNaN(n))
const nextNumber = existingNumbers.length > 0 ? Math.max(...existingNumbers) + 1 : 1
return `${prefix}${nextNumber.toString().padStart(3, '0')}`
}
/**
* Format date for display
*/
export function formatDate(date: Date | string | undefined): string {
if (!date) return '-'
const d = typeof date === 'string' ? new Date(date) : date
return d.toLocaleDateString('de-DE', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
})
}
/**
* Get severity color class
*/
export function getSeverityColor(severity: FindingSeverity): string {
switch (severity) {
case 'LOW': return 'text-blue-600 bg-blue-100'
case 'MEDIUM': return 'text-yellow-600 bg-yellow-100'
case 'HIGH': return 'text-orange-600 bg-orange-100'
case 'CRITICAL': return 'text-red-600 bg-red-100'
}
}
/**
* Get status color class
*/
export function getStatusColor(status: VendorStatus | ProcessingActivityStatus | ContractStatus): string {
switch (status) {
case 'ACTIVE':
case 'APPROVED':
case 'SIGNED':
return 'text-green-600 bg-green-100'
case 'DRAFT':
case 'PENDING_REVIEW':
return 'text-yellow-600 bg-yellow-100'
case 'REVIEW':
case 'INACTIVE':
return 'text-blue-600 bg-blue-100'
case 'ARCHIVED':
case 'EXPIRED':
case 'TERMINATED':
return 'text-gray-600 bg-gray-100'
default:
return 'text-gray-600 bg-gray-100'
}
}
// ==========================================
// CONSTANTS - METADATA
// ==========================================
export const DATA_SUBJECT_CATEGORY_META: Record<DataSubjectCategory, LocalizedText> = {
EMPLOYEES: { de: 'Beschäftigte', en: 'Employees' },
APPLICANTS: { de: 'Bewerber', en: 'Applicants' },
CUSTOMERS: { de: 'Kunden', en: 'Customers' },
PROSPECTIVE_CUSTOMERS: { de: 'Interessenten', en: 'Prospective Customers' },
SUPPLIERS: { de: 'Lieferanten', en: 'Suppliers' },
BUSINESS_PARTNERS: { de: 'Geschäftspartner', en: 'Business Partners' },
VISITORS: { de: 'Besucher', en: 'Visitors' },
WEBSITE_USERS: { de: 'Website-Nutzer', en: 'Website Users' },
APP_USERS: { de: 'App-Nutzer', en: 'App Users' },
NEWSLETTER_SUBSCRIBERS: { de: 'Newsletter-Abonnenten', en: 'Newsletter Subscribers' },
MEMBERS: { de: 'Mitglieder', en: 'Members' },
PATIENTS: { de: 'Patienten', en: 'Patients' },
STUDENTS: { de: 'Schüler/Studenten', en: 'Students' },
MINORS: { de: 'Minderjährige', en: 'Minors' },
OTHER: { de: 'Sonstige', en: 'Other' },
}
export const PERSONAL_DATA_CATEGORY_META: Record<PersonalDataCategory, { label: LocalizedText; isSpecial: boolean }> = {
NAME: { label: { de: 'Name', en: 'Name' }, isSpecial: false },
CONTACT: { label: { de: 'Kontaktdaten', en: 'Contact Data' }, isSpecial: false },
ADDRESS: { label: { de: 'Adressdaten', en: 'Address Data' }, isSpecial: false },
DOB: { label: { de: 'Geburtsdatum', en: 'Date of Birth' }, isSpecial: false },
ID_NUMBER: { label: { de: 'Ausweisnummern', en: 'ID Numbers' }, isSpecial: false },
SOCIAL_SECURITY: { label: { de: 'Sozialversicherungsnummer', en: 'Social Security Number' }, isSpecial: false },
TAX_ID: { label: { de: 'Steuer-ID', en: 'Tax ID' }, isSpecial: false },
BANK_ACCOUNT: { label: { de: 'Bankverbindung', en: 'Bank Account' }, isSpecial: false },
PAYMENT_DATA: { label: { de: 'Zahlungsdaten', en: 'Payment Data' }, isSpecial: false },
EMPLOYMENT_DATA: { label: { de: 'Beschäftigungsdaten', en: 'Employment Data' }, isSpecial: false },
SALARY_DATA: { label: { de: 'Gehaltsdaten', en: 'Salary Data' }, isSpecial: false },
EDUCATION_DATA: { label: { de: 'Bildungsdaten', en: 'Education Data' }, isSpecial: false },
PHOTO_VIDEO: { label: { de: 'Fotos/Videos', en: 'Photos/Videos' }, isSpecial: false },
IP_ADDRESS: { label: { de: 'IP-Adressen', en: 'IP Addresses' }, isSpecial: false },
DEVICE_ID: { label: { de: 'Gerätekennungen', en: 'Device IDs' }, isSpecial: false },
LOCATION_DATA: { label: { de: 'Standortdaten', en: 'Location Data' }, isSpecial: false },
USAGE_DATA: { label: { de: 'Nutzungsdaten', en: 'Usage Data' }, isSpecial: false },
COMMUNICATION_DATA: { label: { de: 'Kommunikationsdaten', en: 'Communication Data' }, isSpecial: false },
CONTRACT_DATA: { label: { de: 'Vertragsdaten', en: 'Contract Data' }, isSpecial: false },
LOGIN_DATA: { label: { de: 'Login-Daten', en: 'Login Data' }, isSpecial: false },
HEALTH_DATA: { label: { de: 'Gesundheitsdaten', en: 'Health Data' }, isSpecial: true },
GENETIC_DATA: { label: { de: 'Genetische Daten', en: 'Genetic Data' }, isSpecial: true },
BIOMETRIC_DATA: { label: { de: 'Biometrische Daten', en: 'Biometric Data' }, isSpecial: true },
RACIAL_ETHNIC: { label: { de: 'Rassische/Ethnische Herkunft', en: 'Racial/Ethnic Origin' }, isSpecial: true },
POLITICAL_OPINIONS: { label: { de: 'Politische Meinungen', en: 'Political Opinions' }, isSpecial: true },
RELIGIOUS_BELIEFS: { label: { de: 'Religiöse Überzeugungen', en: 'Religious Beliefs' }, isSpecial: true },
TRADE_UNION: { label: { de: 'Gewerkschaftszugehörigkeit', en: 'Trade Union Membership' }, isSpecial: true },
SEX_LIFE: { label: { de: 'Sexualleben/Orientierung', en: 'Sex Life/Orientation' }, isSpecial: true },
CRIMINAL_DATA: { label: { de: 'Strafrechtliche Daten', en: 'Criminal Data' }, isSpecial: true },
OTHER: { label: { de: 'Sonstige', en: 'Other' }, isSpecial: false },
}
export const LEGAL_BASIS_META: Record<LegalBasisType, { label: LocalizedText; article: string }> = {
CONSENT: { label: { de: 'Einwilligung', en: 'Consent' }, article: 'Art. 6 Abs. 1 lit. a DSGVO' },
CONTRACT: { label: { de: 'Vertragserfüllung', en: 'Contract Performance' }, article: 'Art. 6 Abs. 1 lit. b DSGVO' },
LEGAL_OBLIGATION: { label: { de: 'Rechtliche Verpflichtung', en: 'Legal Obligation' }, article: 'Art. 6 Abs. 1 lit. c DSGVO' },
VITAL_INTEREST: { label: { de: 'Lebenswichtige Interessen', en: 'Vital Interests' }, article: 'Art. 6 Abs. 1 lit. d DSGVO' },
PUBLIC_TASK: { label: { de: 'Öffentliche Aufgabe', en: 'Public Task' }, article: 'Art. 6 Abs. 1 lit. e DSGVO' },
LEGITIMATE_INTEREST: { label: { de: 'Berechtigtes Interesse', en: 'Legitimate Interest' }, article: 'Art. 6 Abs. 1 lit. f DSGVO' },
ART9_CONSENT: { label: { de: 'Ausdrückliche Einwilligung', en: 'Explicit Consent' }, article: 'Art. 9 Abs. 2 lit. a DSGVO' },
ART9_EMPLOYMENT: { label: { de: 'Arbeitsrecht', en: 'Employment Law' }, article: 'Art. 9 Abs. 2 lit. b DSGVO' },
ART9_VITAL_INTEREST: { label: { de: 'Lebenswichtige Interessen', en: 'Vital Interests' }, article: 'Art. 9 Abs. 2 lit. c DSGVO' },
ART9_FOUNDATION: { label: { de: 'Stiftung/Verein', en: 'Foundation/Association' }, article: 'Art. 9 Abs. 2 lit. d DSGVO' },
ART9_PUBLIC: { label: { de: 'Offenkundig öffentlich', en: 'Manifestly Public' }, article: 'Art. 9 Abs. 2 lit. e DSGVO' },
ART9_LEGAL_CLAIMS: { label: { de: 'Rechtsansprüche', en: 'Legal Claims' }, article: 'Art. 9 Abs. 2 lit. f DSGVO' },
ART9_PUBLIC_INTEREST: { label: { de: 'Öffentliches Interesse', en: 'Public Interest' }, article: 'Art. 9 Abs. 2 lit. g DSGVO' },
ART9_HEALTH: { label: { de: 'Gesundheitsversorgung', en: 'Health Care' }, article: 'Art. 9 Abs. 2 lit. h DSGVO' },
ART9_PUBLIC_HEALTH: { label: { de: 'Öffentliche Gesundheit', en: 'Public Health' }, article: 'Art. 9 Abs. 2 lit. i DSGVO' },
ART9_ARCHIVING: { label: { de: 'Archivzwecke', en: 'Archiving Purposes' }, article: 'Art. 9 Abs. 2 lit. j DSGVO' },
}
export const VENDOR_ROLE_META: Record<VendorRole, LocalizedText> = {
PROCESSOR: { de: 'Auftragsverarbeiter', en: 'Processor' },
JOINT_CONTROLLER: { de: 'Gemeinsam Verantwortlicher', en: 'Joint Controller' },
CONTROLLER: { de: 'Eigenständiger Verantwortlicher', en: 'Independent Controller' },
SUB_PROCESSOR: { de: 'Unterauftragnehmer', en: 'Sub-Processor' },
THIRD_PARTY: { de: 'Dritter', en: 'Third Party' },
}
export const SERVICE_CATEGORY_META: Record<ServiceCategory, LocalizedText> = {
HOSTING: { de: 'Hosting', en: 'Hosting' },
CLOUD_INFRASTRUCTURE: { de: 'Cloud-Infrastruktur', en: 'Cloud Infrastructure' },
ANALYTICS: { de: 'Analytics', en: 'Analytics' },
CRM: { de: 'CRM', en: 'CRM' },
ERP: { de: 'ERP', en: 'ERP' },
HR_SOFTWARE: { de: 'HR-Software', en: 'HR Software' },
PAYMENT: { de: 'Zahlungsabwicklung', en: 'Payment Processing' },
EMAIL: { de: 'E-Mail', en: 'Email' },
MARKETING: { de: 'Marketing', en: 'Marketing' },
SUPPORT: { de: 'Support', en: 'Support' },
SECURITY: { de: 'Sicherheit', en: 'Security' },
INTEGRATION: { de: 'Integration', en: 'Integration' },
CONSULTING: { de: 'Beratung', en: 'Consulting' },
LEGAL: { de: 'Rechtliches', en: 'Legal' },
ACCOUNTING: { de: 'Buchhaltung', en: 'Accounting' },
COMMUNICATION: { de: 'Kommunikation', en: 'Communication' },
STORAGE: { de: 'Speicher', en: 'Storage' },
BACKUP: { de: 'Backup', en: 'Backup' },
CDN: { de: 'CDN', en: 'CDN' },
OTHER: { de: 'Sonstige', en: 'Other' },
}
export const DOCUMENT_TYPE_META: Record<DocumentType, LocalizedText> = {
AVV: { de: 'Auftragsverarbeitungsvertrag', en: 'Data Processing Agreement' },
MSA: { de: 'Rahmenvertrag', en: 'Master Service Agreement' },
SLA: { de: 'Service Level Agreement', en: 'Service Level Agreement' },
SCC: { de: 'Standardvertragsklauseln', en: 'Standard Contractual Clauses' },
NDA: { de: 'Geheimhaltungsvereinbarung', en: 'Non-Disclosure Agreement' },
TOM_ANNEX: { de: 'TOM-Anlage', en: 'TOM Annex' },
CERTIFICATION: { de: 'Zertifikat', en: 'Certification' },
SUB_PROCESSOR_LIST: { de: 'Unterauftragnehmer-Liste', en: 'Sub-Processor List' },
OTHER: { de: 'Sonstige', en: 'Other' },
}
export const TRANSFER_MECHANISM_META: Record<TransferMechanismType, LocalizedText> = {
ADEQUACY_DECISION: { de: 'Angemessenheitsbeschluss', en: 'Adequacy Decision' },
SCC_CONTROLLER: { de: 'SCC (Controller-to-Controller)', en: 'SCC (Controller-to-Controller)' },
SCC_PROCESSOR: { de: 'SCC (Controller-to-Processor)', en: 'SCC (Controller-to-Processor)' },
BCR: { de: 'Binding Corporate Rules', en: 'Binding Corporate Rules' },
DEROGATION_CONSENT: { de: 'Ausdrückliche Einwilligung', en: 'Explicit Consent' },
DEROGATION_CONTRACT: { de: 'Vertragserfüllung', en: 'Contract Performance' },
DEROGATION_LEGAL: { de: 'Rechtsansprüche', en: 'Legal Claims' },
DEROGATION_PUBLIC: { de: 'Öffentliches Interesse', en: 'Public Interest' },
CERTIFICATION: { de: 'Zertifizierung', en: 'Certification' },
CODE_OF_CONDUCT: { de: 'Verhaltensregeln', en: 'Code of Conduct' },
}

View File

@@ -0,0 +1,213 @@
/**
* Processing Activity Types (VVT / RoPA)
*
* Types for Art. 30 DSGVO processing activities:
* - Enums for data categories, legal bases, transfer mechanisms
* - Interfaces for processing activities and related structures
*/
import type {
LocalizedText,
Contact,
ResponsibleParty,
} from './types-common'
// ==========================================
// ENUMS - VVT / PROCESSING ACTIVITIES
// ==========================================
export type ProcessingActivityStatus = 'DRAFT' | 'REVIEW' | 'APPROVED' | 'ARCHIVED'
export type ProtectionLevel = 'LOW' | 'MEDIUM' | 'HIGH'
export type DataSubjectCategory =
| 'EMPLOYEES' // Beschaeftigte
| 'APPLICANTS' // Bewerber
| 'CUSTOMERS' // Kunden
| 'PROSPECTIVE_CUSTOMERS' // Interessenten
| 'SUPPLIERS' // Lieferanten
| 'BUSINESS_PARTNERS' // Geschaeftspartner
| 'VISITORS' // Besucher
| 'WEBSITE_USERS' // Website-Nutzer
| 'APP_USERS' // App-Nutzer
| 'NEWSLETTER_SUBSCRIBERS' // Newsletter-Abonnenten
| 'MEMBERS' // Mitglieder
| 'PATIENTS' // Patienten
| 'STUDENTS' // Schueler/Studenten
| 'MINORS' // Minderjaehrige
| 'OTHER'
export type PersonalDataCategory =
| 'NAME' // Name
| 'CONTACT' // Kontaktdaten
| 'ADDRESS' // Adressdaten
| 'DOB' // Geburtsdatum
| 'ID_NUMBER' // Ausweisnummern
| 'SOCIAL_SECURITY' // Sozialversicherungsnummer
| 'TAX_ID' // Steuer-ID
| 'BANK_ACCOUNT' // Bankverbindung
| 'PAYMENT_DATA' // Zahlungsdaten
| 'EMPLOYMENT_DATA' // Beschaeftigungsdaten
| 'SALARY_DATA' // Gehaltsdaten
| 'EDUCATION_DATA' // Bildungsdaten
| 'PHOTO_VIDEO' // Fotos/Videos
| 'IP_ADDRESS' // IP-Adressen
| 'DEVICE_ID' // Geraete-Kennungen
| 'LOCATION_DATA' // Standortdaten
| 'USAGE_DATA' // Nutzungsdaten
| 'COMMUNICATION_DATA' // Kommunikationsdaten
| 'CONTRACT_DATA' // Vertragsdaten
| 'LOGIN_DATA' // Login-Daten
// Besondere Kategorien Art. 9 DSGVO
| 'HEALTH_DATA' // Gesundheitsdaten
| 'GENETIC_DATA' // Genetische Daten
| 'BIOMETRIC_DATA' // Biometrische Daten
| 'RACIAL_ETHNIC' // Rassische/Ethnische Herkunft
| 'POLITICAL_OPINIONS' // Politische Meinungen
| 'RELIGIOUS_BELIEFS' // Religiose Ueberzeugungen
| 'TRADE_UNION' // Gewerkschaftszugehoerigkeit
| 'SEX_LIFE' // Sexualleben/Orientierung
// Art. 10 DSGVO
| 'CRIMINAL_DATA' // Strafrechtliche Daten
| 'OTHER'
export type RecipientCategoryType =
| 'INTERNAL' // Interne Stellen
| 'GROUP_COMPANY' // Konzernunternehmen
| 'PROCESSOR' // Auftragsverarbeiter
| 'CONTROLLER' // Verantwortlicher
| 'AUTHORITY' // Behoerden
| 'OTHER'
export type LegalBasisType =
// Art. 6 Abs. 1 DSGVO
| 'CONSENT' // lit. a - Einwilligung
| 'CONTRACT' // lit. b - Vertragsdurchfuehrung
| 'LEGAL_OBLIGATION' // lit. c - Rechtliche Verpflichtung
| 'VITAL_INTEREST' // lit. d - Lebenswichtige Interessen
| 'PUBLIC_TASK' // lit. e - Oeffentliche Aufgabe
| 'LEGITIMATE_INTEREST' // lit. f - Berechtigtes Interesse
// Art. 9 Abs. 2 DSGVO (besondere Kategorien)
| 'ART9_CONSENT' // lit. a - Ausdrueckliche Einwilligung
| 'ART9_EMPLOYMENT' // lit. b - Arbeitsrecht
| 'ART9_VITAL_INTEREST' // lit. c - Lebenswichtige Interessen
| 'ART9_FOUNDATION' // lit. d - Stiftung/Verein
| 'ART9_PUBLIC' // lit. e - Offenkundig oeffentlich
| 'ART9_LEGAL_CLAIMS' // lit. f - Rechtsansprueche
| 'ART9_PUBLIC_INTEREST'// lit. g - Oeffentliches Interesse
| 'ART9_HEALTH' // lit. h - Gesundheitsversorgung
| 'ART9_PUBLIC_HEALTH' // lit. i - Oeffentliche Gesundheit
| 'ART9_ARCHIVING' // lit. j - Archivzwecke
export type TransferMechanismType =
| 'ADEQUACY_DECISION' // Angemessenheitsbeschluss
| 'SCC_CONTROLLER' // SCC Controller-to-Controller
| 'SCC_PROCESSOR' // SCC Controller-to-Processor
| 'BCR' // Binding Corporate Rules
| 'DEROGATION_CONSENT' // Ausdrueckliche Einwilligung
| 'DEROGATION_CONTRACT' // Vertragserfuellung
| 'DEROGATION_LEGAL' // Rechtsansprueche
| 'DEROGATION_PUBLIC' // Oeffentliches Interesse
| 'CERTIFICATION' // Zertifizierung
| 'CODE_OF_CONDUCT' // Verhaltensregeln
export type DataSourceType =
| 'DATA_SUBJECT' // Betroffene Person selbst
| 'THIRD_PARTY' // Dritte
| 'PUBLIC_SOURCE' // Oeffentliche Quellen
| 'AUTOMATED' // Automatisiert generiert
| 'EMPLOYEE' // Mitarbeiter
| 'OTHER'
// ==========================================
// INTERFACES - VVT / PROCESSING ACTIVITIES
// ==========================================
export interface LegalBasis {
type: LegalBasisType
description?: string
reference?: string // z.B. "§ 26 BDSG"
}
export interface RecipientCategory {
type: RecipientCategoryType
name: string
description?: string
isThirdCountry?: boolean
country?: string
}
export interface ThirdCountryTransfer {
country: string // ISO 3166-1 alpha-2
recipient: string
transferMechanism: TransferMechanismType
sccVersion?: string
tiaCompleted?: boolean
tiaDate?: Date
additionalMeasures?: string[]
}
export interface RetentionPeriod {
duration?: number // in Monaten
durationUnit?: 'DAYS' | 'MONTHS' | 'YEARS'
description: LocalizedText
legalBasis?: string // z.B. "HGB § 257", "AO § 147"
deletionProcedure?: string
}
export interface DataSource {
type: DataSourceType
description?: string
}
export interface SystemReference {
systemId: string
name: string
description?: string
type?: string // CRM, ERP, etc.
}
export interface DataFlow {
sourceSystem?: string
targetSystem?: string
description: string
dataCategories: PersonalDataCategory[]
}
export interface ProcessingActivity {
id: string
tenantId: string
// Pflichtfelder Art. 30(1) DSGVO
vvtId: string // Eindeutige VVT-Nummer (z.B. VVT-2024-001)
name: LocalizedText
responsible: ResponsibleParty
dpoContact?: Contact
purposes: LocalizedText[]
dataSubjectCategories: DataSubjectCategory[]
personalDataCategories: PersonalDataCategory[]
recipientCategories: RecipientCategory[]
thirdCountryTransfers: ThirdCountryTransfer[]
retentionPeriod: RetentionPeriod
technicalMeasures: string[] // TOM-Referenzen
// Empfohlene Zusatzfelder
legalBasis: LegalBasis[]
dataSources: DataSource[]
systems: SystemReference[]
dataFlows: DataFlow[]
protectionLevel: ProtectionLevel
dpiaRequired: boolean
dpiaJustification?: string
subProcessors: string[] // Vendor-IDs
legalRetentionBasis?: string
// Workflow
status: ProcessingActivityStatus
owner: string
lastReviewDate?: Date
nextReviewDate?: Date
createdAt: Date
updatedAt: Date
}

View File

@@ -0,0 +1,147 @@
/**
* Risk & Controls Types
*
* Types for risk assessment and control management:
* - Risk scoring (likelihood x impact matrix)
* - Control definitions and instances
* - Evidence tracking
*/
import type { LocalizedText } from './types-common'
import type { ReviewFrequency } from './types-vendor'
// ==========================================
// ENUMS - RISK & CONTROLS
// ==========================================
export type ControlDomain =
| 'TRANSFER' // Drittlandtransfer
| 'AUDIT' // Auditrechte
| 'DELETION' // Loeschung
| 'INCIDENT' // Incident Response
| 'SUBPROCESSOR' // Unterauftragnehmer
| 'TOM' // Tech/Org Massnahmen
| 'CONTRACT' // Vertragliche Grundlagen
| 'DATA_SUBJECT' // Betroffenenrechte
| 'SECURITY' // Sicherheit
| 'GOVERNANCE' // Governance
export type ControlStatus =
| 'PASS'
| 'PARTIAL'
| 'FAIL'
| 'NOT_APPLICABLE'
| 'PLANNED'
export type RiskLevel = 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL'
export type EntityType = 'VENDOR' | 'PROCESSING_ACTIVITY' | 'CONTRACT'
export type EvidenceType = 'DOCUMENT' | 'SCREENSHOT' | 'LINK' | 'ATTESTATION'
// ==========================================
// INTERFACES - RISK & CONTROLS
// ==========================================
export interface RiskFactor {
id: string
name: LocalizedText
category: string
weight: number
value: number // 1-5
rationale?: string
}
export interface RiskScore {
likelihood: 1 | 2 | 3 | 4 | 5
impact: 1 | 2 | 3 | 4 | 5
score: number // likelihood * impact (1-25)
level: RiskLevel
rationale: string
}
export interface RiskAssessment {
id: string
tenantId: string
entityType: EntityType
entityId: string
// Bewertung
inherentRisk: RiskScore
residualRisk: RiskScore
// Faktoren
riskFactors: RiskFactor[]
mitigatingControls: string[] // Control-IDs
// Workflow
assessedBy: string
assessedAt: Date
approvedBy?: string
approvedAt?: Date
nextAssessmentDate: Date
}
export interface Control {
id: string // z.B. VND-TRF-01
domain: ControlDomain
title: LocalizedText
description: LocalizedText
passCriteria: LocalizedText
// Mapping
requirements: string[] // Art. 28 Abs. 3 lit. a, ISO 27001 A.15.1.2
// Standard
isRequired: boolean
defaultFrequency: ReviewFrequency
}
export interface ControlInstance {
id: string
tenantId: string
controlId: string
entityType: EntityType
entityId: string
// Status
status: ControlStatus
// Evidenz
evidenceIds: string[]
// Workflow
lastAssessedAt: Date
lastAssessedBy: string
nextAssessmentDate: Date
notes?: string
}
export interface Evidence {
id: string
tenantId: string
controlInstanceId: string
type: EvidenceType
title: string
description?: string
// Fuer Dokumente
storagePath?: string
fileName?: string
// Fuer Links
url?: string
// Fuer Attestation
attestedBy?: string
attestedAt?: Date
validFrom: Date
validUntil?: Date
createdAt: Date
}

View File

@@ -0,0 +1,263 @@
/**
* State Management Types
*
* Types for React context state management:
* - Reducer actions (VendorComplianceAction)
* - State shape (VendorComplianceState)
* - Context value with computed properties and action methods
* - Statistics and API response types
* - Form data types
*/
import type React from 'react'
import type { LocalizedText, Contact, ResponsibleParty, Address } from './types-common'
import type {
ProcessingActivity,
ProcessingActivityStatus,
DataSubjectCategory,
PersonalDataCategory,
RecipientCategory,
ThirdCountryTransfer,
RetentionPeriod,
LegalBasis,
DataSource,
SystemReference,
DataFlow,
ProtectionLevel,
TransferMechanismType,
} from './types-processing'
import type {
Vendor,
VendorStatus,
VendorRole,
ServiceCategory,
DataAccessLevel,
ProcessingLocation,
Certification,
ReviewFrequency,
} from './types-vendor'
import type {
ContractDocument,
ContractStatus,
DocumentType,
} from './types-contract'
import type {
Finding,
FindingType,
FindingSeverity,
} from './types-finding'
import type {
Control,
ControlInstance,
RiskAssessment,
RiskLevel,
} from './types-risk'
import type { ExportFormat } from './types-audit'
// ==========================================
// STATE MANAGEMENT - ACTIONS
// ==========================================
export type VendorComplianceAction =
// Processing Activities
| { type: 'SET_PROCESSING_ACTIVITIES'; payload: ProcessingActivity[] }
| { type: 'ADD_PROCESSING_ACTIVITY'; payload: ProcessingActivity }
| { type: 'UPDATE_PROCESSING_ACTIVITY'; payload: { id: string; data: Partial<ProcessingActivity> } }
| { type: 'DELETE_PROCESSING_ACTIVITY'; payload: string }
// Vendors
| { type: 'SET_VENDORS'; payload: Vendor[] }
| { type: 'ADD_VENDOR'; payload: Vendor }
| { type: 'UPDATE_VENDOR'; payload: { id: string; data: Partial<Vendor> } }
| { type: 'DELETE_VENDOR'; payload: string }
// Contracts
| { type: 'SET_CONTRACTS'; payload: ContractDocument[] }
| { type: 'ADD_CONTRACT'; payload: ContractDocument }
| { type: 'UPDATE_CONTRACT'; payload: { id: string; data: Partial<ContractDocument> } }
| { type: 'DELETE_CONTRACT'; payload: string }
// Findings
| { type: 'SET_FINDINGS'; payload: Finding[] }
| { type: 'ADD_FINDINGS'; payload: Finding[] }
| { type: 'UPDATE_FINDING'; payload: { id: string; data: Partial<Finding> } }
// Controls
| { type: 'SET_CONTROLS'; payload: Control[] }
| { type: 'SET_CONTROL_INSTANCES'; payload: ControlInstance[] }
| { type: 'UPDATE_CONTROL_INSTANCE'; payload: { id: string; data: Partial<ControlInstance> } }
// Risk Assessments
| { type: 'SET_RISK_ASSESSMENTS'; payload: RiskAssessment[] }
| { type: 'UPDATE_RISK_ASSESSMENT'; payload: { id: string; data: Partial<RiskAssessment> } }
// UI State
| { type: 'SET_LOADING'; payload: boolean }
| { type: 'SET_ERROR'; payload: string | null }
| { type: 'SET_SELECTED_VENDOR'; payload: string | null }
| { type: 'SET_SELECTED_ACTIVITY'; payload: string | null }
| { type: 'SET_ACTIVE_TAB'; payload: string }
// ==========================================
// STATE MANAGEMENT - STATE
// ==========================================
export interface VendorComplianceState {
// Data
processingActivities: ProcessingActivity[]
vendors: Vendor[]
contracts: ContractDocument[]
findings: Finding[]
controls: Control[]
controlInstances: ControlInstance[]
riskAssessments: RiskAssessment[]
// UI State
isLoading: boolean
error: string | null
selectedVendorId: string | null
selectedActivityId: string | null
activeTab: string
// Metadata
lastModified: Date | null
}
// ==========================================
// STATISTICS INTERFACES
// ==========================================
export interface VendorStatistics {
total: number
byStatus: Record<VendorStatus, number>
byRole: Record<VendorRole, number>
byRiskLevel: Record<RiskLevel, number>
pendingReviews: number
withExpiredContracts: number
}
export interface ComplianceStatistics {
averageComplianceScore: number
findingsByType: Record<FindingType, number>
findingsBySeverity: Record<FindingSeverity, number>
openFindings: number
resolvedFindings: number
controlPassRate: number
}
export interface RiskOverview {
averageInherentRisk: number
averageResidualRisk: number
highRiskVendors: number
criticalFindings: number
transfersToThirdCountries: number
}
// ==========================================
// CONTEXT VALUE
// ==========================================
export interface VendorComplianceContextValue extends VendorComplianceState {
// Dispatch
dispatch: React.Dispatch<VendorComplianceAction>
// Computed
vendorStats: VendorStatistics
complianceStats: ComplianceStatistics
riskOverview: RiskOverview
// Actions - Processing Activities
createProcessingActivity: (data: Omit<ProcessingActivity, 'id' | 'tenantId' | 'createdAt' | 'updatedAt'>) => Promise<ProcessingActivity>
updateProcessingActivity: (id: string, data: Partial<ProcessingActivity>) => Promise<void>
deleteProcessingActivity: (id: string) => Promise<void>
duplicateProcessingActivity: (id: string) => Promise<ProcessingActivity>
// Actions - Vendors
createVendor: (data: Omit<Vendor, 'id' | 'tenantId' | 'createdAt' | 'updatedAt'>) => Promise<Vendor>
updateVendor: (id: string, data: Partial<Vendor>) => Promise<void>
deleteVendor: (id: string) => Promise<void>
// Actions - Contracts
uploadContract: (vendorId: string, file: File, metadata: Partial<ContractDocument>) => Promise<ContractDocument>
updateContract: (id: string, data: Partial<ContractDocument>) => Promise<void>
deleteContract: (id: string) => Promise<void>
startContractReview: (contractId: string) => Promise<void>
// Actions - Findings
updateFinding: (id: string, data: Partial<Finding>) => Promise<void>
resolveFinding: (id: string, resolution: string) => Promise<void>
// Actions - Controls
updateControlInstance: (id: string, data: Partial<ControlInstance>) => Promise<void>
// Actions - Export
exportVVT: (format: ExportFormat, activityIds?: string[]) => Promise<string>
exportVendorAuditPack: (vendorId: string, format: ExportFormat) => Promise<string>
exportRoPA: (format: ExportFormat) => Promise<string>
// Data Loading
loadData: () => Promise<void>
refresh: () => Promise<void>
}
// ==========================================
// API RESPONSE TYPES
// ==========================================
export interface ApiResponse<T> {
success: boolean
data?: T
error?: string
timestamp: string
}
export interface PaginatedResponse<T> extends ApiResponse<T[]> {
pagination: {
page: number
pageSize: number
total: number
totalPages: number
}
}
// ==========================================
// FORM TYPES
// ==========================================
export interface ProcessingActivityFormData {
vvtId: string
name: LocalizedText
responsible: ResponsibleParty
dpoContact?: Contact
purposes: LocalizedText[]
dataSubjectCategories: DataSubjectCategory[]
personalDataCategories: PersonalDataCategory[]
recipientCategories: RecipientCategory[]
thirdCountryTransfers: ThirdCountryTransfer[]
retentionPeriod: RetentionPeriod
technicalMeasures: string[]
legalBasis: LegalBasis[]
dataSources: DataSource[]
systems: SystemReference[]
dataFlows: DataFlow[]
protectionLevel: ProtectionLevel
dpiaRequired: boolean
dpiaJustification?: string
subProcessors: string[]
owner: string
}
export interface VendorFormData {
name: string
legalForm?: string
country: string
address: Address
website?: string
role: VendorRole
serviceDescription: string
serviceCategory: ServiceCategory
dataAccessLevel: DataAccessLevel
processingLocations: ProcessingLocation[]
transferMechanisms: TransferMechanismType[]
certifications: Certification[]
primaryContact: Contact
dpoContact?: Contact
securityContact?: Contact
contractTypes: DocumentType[]
reviewFrequency: ReviewFrequency
notes?: string
}

View File

@@ -0,0 +1,146 @@
/**
* Vendor Types
*
* Types for vendor/supplier management:
* - Vendor roles, statuses, service categories
* - Processing locations, certifications
* - Vendor interface with risk scoring
*/
import type { Address, Contact } from './types-common'
import type { DocumentType } from './types-contract'
import type { TransferMechanismType } from './types-processing'
// ==========================================
// ENUMS - VENDOR
// ==========================================
export type VendorRole =
| 'PROCESSOR' // Auftragsverarbeiter
| 'JOINT_CONTROLLER' // Gemeinsam Verantwortlicher
| 'CONTROLLER' // Eigenstaendiger Verantwortlicher
| 'SUB_PROCESSOR' // Unterauftragnehmer
| 'THIRD_PARTY' // Dritter (kein Datenzugriff)
export type ServiceCategory =
| 'HOSTING'
| 'CLOUD_INFRASTRUCTURE'
| 'ANALYTICS'
| 'CRM'
| 'ERP'
| 'HR_SOFTWARE'
| 'PAYMENT'
| 'EMAIL'
| 'MARKETING'
| 'SUPPORT'
| 'SECURITY'
| 'INTEGRATION'
| 'CONSULTING'
| 'LEGAL'
| 'ACCOUNTING'
| 'COMMUNICATION'
| 'STORAGE'
| 'BACKUP'
| 'CDN'
| 'OTHER'
export type DataAccessLevel =
| 'NONE' // Kein Datenzugriff
| 'POTENTIAL' // Potenzieller Zugriff (z.B. Admin)
| 'ADMINISTRATIVE' // Administrativer Zugriff
| 'CONTENT' // Inhaltlicher Zugriff
export type VendorStatus =
| 'ACTIVE'
| 'INACTIVE'
| 'PENDING_REVIEW'
| 'TERMINATED'
export type ReviewFrequency =
| 'QUARTERLY'
| 'SEMI_ANNUAL'
| 'ANNUAL'
| 'BIENNIAL'
// ==========================================
// INTERFACES - VENDOR
// ==========================================
export interface ProcessingLocation {
country: string // ISO 3166-1 alpha-2
region?: string
city?: string
dataCenter?: string
isEU: boolean
isAdequate: boolean // Angemessenheitsbeschluss
type?: string // e.g., 'primary', 'backup', 'disaster-recovery'
description?: string
isPrimary?: boolean
}
export interface Certification {
type: string // ISO 27001, SOC2, TISAX, C5, etc.
issuer?: string
issuedDate?: Date
expirationDate?: Date
scope?: string
certificateNumber?: string
documentId?: string // Referenz zum hochgeladenen Zertifikat
}
export interface Vendor {
id: string
tenantId: string
// Stammdaten
name: string
legalForm?: string
country: string
address: Address
website?: string
// Rolle
role: VendorRole
serviceDescription: string
serviceCategory: ServiceCategory
// Datenzugriff
dataAccessLevel: DataAccessLevel
processingLocations: ProcessingLocation[]
transferMechanisms: TransferMechanismType[]
// Zertifizierungen
certifications: Certification[]
// Kontakte
primaryContact: Contact
dpoContact?: Contact
securityContact?: Contact
// Vertraege
contractTypes: DocumentType[]
contracts: string[] // Contract-IDs
// Risiko
inherentRiskScore: number // 0-100 (auto-berechnet)
residualRiskScore: number // 0-100 (nach Controls)
manualRiskAdjustment?: number
riskJustification?: string
// Review
reviewFrequency: ReviewFrequency
lastReviewDate?: Date
nextReviewDate?: Date
// Workflow
status: VendorStatus
// Linked Processing Activities
processingActivityIds: string[]
// Notes
notes?: string
createdAt: Date
updatedAt: Date
}

File diff suppressed because it is too large Load Diff