[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:
491
admin-lehrer/lib/sdk/vendor-compliance/context-actions.ts
Normal file
491
admin-lehrer/lib/sdk/vendor-compliance/context-actions.ts
Normal 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
|
||||
}
|
||||
334
admin-lehrer/lib/sdk/vendor-compliance/context-reducer.ts
Normal file
334
admin-lehrer/lib/sdk/vendor-compliance/context-reducer.ts
Normal 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,
|
||||
}
|
||||
}
|
||||
@@ -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]
|
||||
[]
|
||||
)
|
||||
|
||||
// ==========================================
|
||||
|
||||
37
admin-lehrer/lib/sdk/vendor-compliance/risk/controls-all.ts
Normal file
37
admin-lehrer/lib/sdk/vendor-compliance/risk/controls-all.ts
Normal 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,
|
||||
]
|
||||
@@ -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',
|
||||
},
|
||||
]
|
||||
@@ -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',
|
||||
},
|
||||
]
|
||||
115
admin-lehrer/lib/sdk/vendor-compliance/risk/controls-helpers.ts
Normal file
115
admin-lehrer/lib/sdk/vendor-compliance/risk/controls-helpers.ts
Normal 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,
|
||||
}
|
||||
}
|
||||
@@ -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'
|
||||
|
||||
50
admin-lehrer/lib/sdk/vendor-compliance/types-audit.ts
Normal file
50
admin-lehrer/lib/sdk/vendor-compliance/types-audit.ts
Normal 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
|
||||
}
|
||||
58
admin-lehrer/lib/sdk/vendor-compliance/types-common.ts
Normal file
58
admin-lehrer/lib/sdk/vendor-compliance/types-common.ts
Normal 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
|
||||
}
|
||||
117
admin-lehrer/lib/sdk/vendor-compliance/types-contract.ts
Normal file
117
admin-lehrer/lib/sdk/vendor-compliance/types-contract.ts
Normal 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
|
||||
}
|
||||
93
admin-lehrer/lib/sdk/vendor-compliance/types-finding.ts
Normal file
93
admin-lehrer/lib/sdk/vendor-compliance/types-finding.ts
Normal 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
|
||||
}
|
||||
280
admin-lehrer/lib/sdk/vendor-compliance/types-helpers.ts
Normal file
280
admin-lehrer/lib/sdk/vendor-compliance/types-helpers.ts
Normal 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' },
|
||||
}
|
||||
213
admin-lehrer/lib/sdk/vendor-compliance/types-processing.ts
Normal file
213
admin-lehrer/lib/sdk/vendor-compliance/types-processing.ts
Normal 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
|
||||
}
|
||||
147
admin-lehrer/lib/sdk/vendor-compliance/types-risk.ts
Normal file
147
admin-lehrer/lib/sdk/vendor-compliance/types-risk.ts
Normal 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
|
||||
}
|
||||
263
admin-lehrer/lib/sdk/vendor-compliance/types-state.ts
Normal file
263
admin-lehrer/lib/sdk/vendor-compliance/types-state.ts
Normal 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
|
||||
}
|
||||
146
admin-lehrer/lib/sdk/vendor-compliance/types-vendor.ts
Normal file
146
admin-lehrer/lib/sdk/vendor-compliance/types-vendor.ts
Normal 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
Reference in New Issue
Block a user