- /sdk/consent: Replace hardcoded mockDocuments with GET /api/admin/consent/documents - /sdk/dsr: Replace createMockDSRList with fetchSDKDSRList via /api/sdk/v1/dsgvo/dsr - /sdk/dsr/new: Replace console.log mock with real POST to create DSR requests - /sdk/dsr/[requestId]: Replace mock lookup with real GET/PUT for DSR details and status updates - /sdk/consent-management: Add real stats, GDPR process counts, and email template editor - lib/sdk/dsr/api.ts: Add transformBackendDSR adapter (flat backend → nested frontend types) Prepares for removal of /dsgvo and /compliance pages. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
882 lines
26 KiB
TypeScript
882 lines
26 KiB
TypeScript
/**
|
|
* DSR API Client
|
|
*
|
|
* API client for Data Subject Request management
|
|
* Connects to the Go Consent Service backend
|
|
*/
|
|
|
|
import {
|
|
DSRRequest,
|
|
DSRListResponse,
|
|
DSRFilters,
|
|
DSRCreateRequest,
|
|
DSRUpdateRequest,
|
|
DSRVerifyIdentityRequest,
|
|
DSRCompleteRequest,
|
|
DSRRejectRequest,
|
|
DSRExtendDeadlineRequest,
|
|
DSRSendCommunicationRequest,
|
|
DSRCommunication,
|
|
DSRAuditEntry,
|
|
DSRStatistics,
|
|
DSRDataExport,
|
|
DSRErasureChecklist
|
|
} from './types'
|
|
|
|
// =============================================================================
|
|
// CONFIGURATION
|
|
// =============================================================================
|
|
|
|
const DSR_API_BASE = process.env.NEXT_PUBLIC_CONSENT_SERVICE_URL || 'http://localhost:8081'
|
|
const API_TIMEOUT = 30000 // 30 seconds
|
|
|
|
// =============================================================================
|
|
// HELPER FUNCTIONS
|
|
// =============================================================================
|
|
|
|
function getTenantId(): string {
|
|
// In a real app, this would come from auth context or localStorage
|
|
if (typeof window !== 'undefined') {
|
|
return localStorage.getItem('tenantId') || 'default-tenant'
|
|
}
|
|
return 'default-tenant'
|
|
}
|
|
|
|
function getAuthHeaders(): HeadersInit {
|
|
const headers: HeadersInit = {
|
|
'Content-Type': 'application/json',
|
|
'X-Tenant-ID': getTenantId()
|
|
}
|
|
|
|
// Add auth token if available
|
|
if (typeof window !== 'undefined') {
|
|
const token = localStorage.getItem('authToken')
|
|
if (token) {
|
|
headers['Authorization'] = `Bearer ${token}`
|
|
}
|
|
}
|
|
|
|
return headers
|
|
}
|
|
|
|
async function fetchWithTimeout<T>(
|
|
url: string,
|
|
options: RequestInit = {},
|
|
timeout: number = API_TIMEOUT
|
|
): Promise<T> {
|
|
const controller = new AbortController()
|
|
const timeoutId = setTimeout(() => controller.abort(), timeout)
|
|
|
|
try {
|
|
const response = await fetch(url, {
|
|
...options,
|
|
signal: controller.signal,
|
|
headers: {
|
|
...getAuthHeaders(),
|
|
...options.headers
|
|
}
|
|
})
|
|
|
|
if (!response.ok) {
|
|
const errorBody = await response.text()
|
|
let errorMessage = `HTTP ${response.status}: ${response.statusText}`
|
|
try {
|
|
const errorJson = JSON.parse(errorBody)
|
|
errorMessage = errorJson.error || errorJson.message || errorMessage
|
|
} catch {
|
|
// Keep the HTTP status message
|
|
}
|
|
throw new Error(errorMessage)
|
|
}
|
|
|
|
// Handle empty responses
|
|
const contentType = response.headers.get('content-type')
|
|
if (contentType && contentType.includes('application/json')) {
|
|
return response.json()
|
|
}
|
|
|
|
return {} as T
|
|
} finally {
|
|
clearTimeout(timeoutId)
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// DSR LIST & CRUD
|
|
// =============================================================================
|
|
|
|
/**
|
|
* Fetch all DSR requests with optional filters
|
|
*/
|
|
export async function fetchDSRList(filters?: DSRFilters): Promise<DSRListResponse> {
|
|
const params = new URLSearchParams()
|
|
|
|
if (filters) {
|
|
if (filters.status) {
|
|
const statuses = Array.isArray(filters.status) ? filters.status : [filters.status]
|
|
statuses.forEach(s => params.append('status', s))
|
|
}
|
|
if (filters.type) {
|
|
const types = Array.isArray(filters.type) ? filters.type : [filters.type]
|
|
types.forEach(t => params.append('type', t))
|
|
}
|
|
if (filters.priority) params.set('priority', filters.priority)
|
|
if (filters.assignedTo) params.set('assignedTo', filters.assignedTo)
|
|
if (filters.overdue !== undefined) params.set('overdue', String(filters.overdue))
|
|
if (filters.search) params.set('search', filters.search)
|
|
if (filters.dateFrom) params.set('dateFrom', filters.dateFrom)
|
|
if (filters.dateTo) params.set('dateTo', filters.dateTo)
|
|
}
|
|
|
|
const queryString = params.toString()
|
|
const url = `${DSR_API_BASE}/api/v1/admin/dsr${queryString ? `?${queryString}` : ''}`
|
|
|
|
return fetchWithTimeout<DSRListResponse>(url)
|
|
}
|
|
|
|
/**
|
|
* Fetch a single DSR request by ID
|
|
*/
|
|
export async function fetchDSR(id: string): Promise<DSRRequest> {
|
|
return fetchWithTimeout<DSRRequest>(`${DSR_API_BASE}/api/v1/admin/dsr/${id}`)
|
|
}
|
|
|
|
/**
|
|
* Create a new DSR request
|
|
*/
|
|
export async function createDSR(request: DSRCreateRequest): Promise<DSRRequest> {
|
|
return fetchWithTimeout<DSRRequest>(`${DSR_API_BASE}/api/v1/admin/dsr`, {
|
|
method: 'POST',
|
|
body: JSON.stringify(request)
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Update a DSR request
|
|
*/
|
|
export async function updateDSR(id: string, update: DSRUpdateRequest): Promise<DSRRequest> {
|
|
return fetchWithTimeout<DSRRequest>(`${DSR_API_BASE}/api/v1/admin/dsr/${id}`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(update)
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Delete a DSR request (soft delete - marks as cancelled)
|
|
*/
|
|
export async function deleteDSR(id: string): Promise<void> {
|
|
await fetchWithTimeout<void>(`${DSR_API_BASE}/api/v1/admin/dsr/${id}`, {
|
|
method: 'DELETE'
|
|
})
|
|
}
|
|
|
|
// =============================================================================
|
|
// DSR WORKFLOW ACTIONS
|
|
// =============================================================================
|
|
|
|
/**
|
|
* Verify the identity of the requester
|
|
*/
|
|
export async function verifyIdentity(
|
|
dsrId: string,
|
|
verification: DSRVerifyIdentityRequest
|
|
): Promise<DSRRequest> {
|
|
return fetchWithTimeout<DSRRequest>(
|
|
`${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/verify-identity`,
|
|
{
|
|
method: 'POST',
|
|
body: JSON.stringify(verification)
|
|
}
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Complete a DSR request
|
|
*/
|
|
export async function completeDSR(
|
|
dsrId: string,
|
|
completion?: DSRCompleteRequest
|
|
): Promise<DSRRequest> {
|
|
return fetchWithTimeout<DSRRequest>(
|
|
`${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/complete`,
|
|
{
|
|
method: 'POST',
|
|
body: JSON.stringify(completion || {})
|
|
}
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Reject a DSR request
|
|
*/
|
|
export async function rejectDSR(
|
|
dsrId: string,
|
|
rejection: DSRRejectRequest
|
|
): Promise<DSRRequest> {
|
|
return fetchWithTimeout<DSRRequest>(
|
|
`${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/reject`,
|
|
{
|
|
method: 'POST',
|
|
body: JSON.stringify(rejection)
|
|
}
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Extend the deadline for a DSR request
|
|
*/
|
|
export async function extendDeadline(
|
|
dsrId: string,
|
|
extension: DSRExtendDeadlineRequest
|
|
): Promise<DSRRequest> {
|
|
return fetchWithTimeout<DSRRequest>(
|
|
`${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/extend`,
|
|
{
|
|
method: 'POST',
|
|
body: JSON.stringify(extension)
|
|
}
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Assign a DSR request to a user
|
|
*/
|
|
export async function assignDSR(
|
|
dsrId: string,
|
|
assignedTo: string
|
|
): Promise<DSRRequest> {
|
|
return fetchWithTimeout<DSRRequest>(
|
|
`${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/assign`,
|
|
{
|
|
method: 'POST',
|
|
body: JSON.stringify({ assignedTo })
|
|
}
|
|
)
|
|
}
|
|
|
|
// =============================================================================
|
|
// COMMUNICATION
|
|
// =============================================================================
|
|
|
|
/**
|
|
* Get all communications for a DSR request
|
|
*/
|
|
export async function getCommunications(dsrId: string): Promise<DSRCommunication[]> {
|
|
return fetchWithTimeout<DSRCommunication[]>(
|
|
`${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/communications`
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Send a communication (email, letter, internal note)
|
|
*/
|
|
export async function sendCommunication(
|
|
dsrId: string,
|
|
communication: DSRSendCommunicationRequest
|
|
): Promise<DSRCommunication> {
|
|
return fetchWithTimeout<DSRCommunication>(
|
|
`${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/send-communication`,
|
|
{
|
|
method: 'POST',
|
|
body: JSON.stringify(communication)
|
|
}
|
|
)
|
|
}
|
|
|
|
// =============================================================================
|
|
// AUDIT LOG
|
|
// =============================================================================
|
|
|
|
/**
|
|
* Get audit log entries for a DSR request
|
|
*/
|
|
export async function getAuditLog(dsrId: string): Promise<DSRAuditEntry[]> {
|
|
return fetchWithTimeout<DSRAuditEntry[]>(
|
|
`${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/audit`
|
|
)
|
|
}
|
|
|
|
// =============================================================================
|
|
// STATISTICS
|
|
// =============================================================================
|
|
|
|
/**
|
|
* Get DSR statistics
|
|
*/
|
|
export async function getDSRStatistics(): Promise<DSRStatistics> {
|
|
return fetchWithTimeout<DSRStatistics>(
|
|
`${DSR_API_BASE}/api/v1/admin/dsr/statistics`
|
|
)
|
|
}
|
|
|
|
// =============================================================================
|
|
// DATA EXPORT (Art. 15, 20)
|
|
// =============================================================================
|
|
|
|
/**
|
|
* Generate data export for Art. 15 (access) or Art. 20 (portability)
|
|
*/
|
|
export async function generateDataExport(
|
|
dsrId: string,
|
|
format: 'json' | 'csv' | 'xml' | 'pdf' = 'json'
|
|
): Promise<DSRDataExport> {
|
|
return fetchWithTimeout<DSRDataExport>(
|
|
`${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/export`,
|
|
{
|
|
method: 'POST',
|
|
body: JSON.stringify({ format })
|
|
}
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Download generated data export
|
|
*/
|
|
export async function downloadDataExport(dsrId: string): Promise<Blob> {
|
|
const response = await fetch(
|
|
`${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/export/download`,
|
|
{
|
|
headers: getAuthHeaders()
|
|
}
|
|
)
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Download failed: ${response.statusText}`)
|
|
}
|
|
|
|
return response.blob()
|
|
}
|
|
|
|
// =============================================================================
|
|
// ERASURE CHECKLIST (Art. 17)
|
|
// =============================================================================
|
|
|
|
/**
|
|
* Get the erasure checklist for an Art. 17 request
|
|
*/
|
|
export async function getErasureChecklist(dsrId: string): Promise<DSRErasureChecklist> {
|
|
return fetchWithTimeout<DSRErasureChecklist>(
|
|
`${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/erasure-checklist`
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Update the erasure checklist
|
|
*/
|
|
export async function updateErasureChecklist(
|
|
dsrId: string,
|
|
checklist: DSRErasureChecklist
|
|
): Promise<DSRErasureChecklist> {
|
|
return fetchWithTimeout<DSRErasureChecklist>(
|
|
`${DSR_API_BASE}/api/v1/admin/dsr/${dsrId}/erasure-checklist`,
|
|
{
|
|
method: 'PUT',
|
|
body: JSON.stringify(checklist)
|
|
}
|
|
)
|
|
}
|
|
|
|
// =============================================================================
|
|
// EMAIL TEMPLATES
|
|
// =============================================================================
|
|
|
|
/**
|
|
* Get available email templates
|
|
*/
|
|
export async function getEmailTemplates(): Promise<{ id: string; name: string; stage: string }[]> {
|
|
return fetchWithTimeout<{ id: string; name: string; stage: string }[]>(
|
|
`${DSR_API_BASE}/api/v1/admin/dsr/email-templates`
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Preview an email with variables filled in
|
|
*/
|
|
export async function previewEmail(
|
|
templateId: string,
|
|
dsrId: string
|
|
): Promise<{ subject: string; body: string }> {
|
|
return fetchWithTimeout<{ subject: string; body: string }>(
|
|
`${DSR_API_BASE}/api/v1/admin/dsr/email-templates/${templateId}/preview`,
|
|
{
|
|
method: 'POST',
|
|
body: JSON.stringify({ dsrId })
|
|
}
|
|
)
|
|
}
|
|
|
|
// =============================================================================
|
|
// SDK API FUNCTIONS (via Next.js proxy to ai-compliance-sdk)
|
|
// =============================================================================
|
|
|
|
interface BackendDSR {
|
|
id: string
|
|
tenant_id: string
|
|
namespace_id?: string
|
|
request_type: string
|
|
status: string
|
|
subject_name: string
|
|
subject_email: string
|
|
subject_identifier?: string
|
|
request_description: string
|
|
request_channel: string
|
|
received_at: string
|
|
verified_at?: string
|
|
verification_method?: string
|
|
deadline_at: string
|
|
extended_deadline_at?: string
|
|
extension_reason?: string
|
|
completed_at?: string
|
|
response_sent: boolean
|
|
response_sent_at?: string
|
|
response_method?: string
|
|
rejection_reason?: string
|
|
notes?: string
|
|
affected_systems?: string[]
|
|
assigned_to?: string
|
|
created_at: string
|
|
updated_at: string
|
|
}
|
|
|
|
function mapBackendStatus(status: string): import('./types').DSRStatus {
|
|
const mapping: Record<string, import('./types').DSRStatus> = {
|
|
'received': 'intake',
|
|
'verified': 'identity_verification',
|
|
'in_progress': 'processing',
|
|
'completed': 'completed',
|
|
'rejected': 'rejected',
|
|
'extended': 'processing',
|
|
}
|
|
return mapping[status] || 'intake'
|
|
}
|
|
|
|
function mapBackendChannel(channel: string): import('./types').DSRSource {
|
|
const mapping: Record<string, import('./types').DSRSource> = {
|
|
'email': 'email',
|
|
'form': 'web_form',
|
|
'phone': 'phone',
|
|
'letter': 'letter',
|
|
}
|
|
return mapping[channel] || 'other'
|
|
}
|
|
|
|
/**
|
|
* Transform flat backend DSR to nested SDK DSRRequest format
|
|
*/
|
|
export function transformBackendDSR(b: BackendDSR): DSRRequest {
|
|
const deadlineAt = b.extended_deadline_at || b.deadline_at
|
|
const receivedDate = new Date(b.received_at)
|
|
const defaultDeadlineDays = 30
|
|
const originalDeadline = b.deadline_at || new Date(receivedDate.getTime() + defaultDeadlineDays * 24 * 60 * 60 * 1000).toISOString()
|
|
|
|
return {
|
|
id: b.id,
|
|
referenceNumber: `DSR-${new Date(b.created_at).getFullYear()}-${b.id.slice(0, 6).toUpperCase()}`,
|
|
type: b.request_type as DSRRequest['type'],
|
|
status: mapBackendStatus(b.status),
|
|
priority: 'normal',
|
|
requester: {
|
|
name: b.subject_name,
|
|
email: b.subject_email,
|
|
customerId: b.subject_identifier,
|
|
},
|
|
source: mapBackendChannel(b.request_channel),
|
|
requestText: b.request_description,
|
|
receivedAt: b.received_at,
|
|
deadline: {
|
|
originalDeadline,
|
|
currentDeadline: deadlineAt,
|
|
extended: !!b.extended_deadline_at,
|
|
extensionReason: b.extension_reason,
|
|
},
|
|
completedAt: b.completed_at,
|
|
identityVerification: {
|
|
verified: !!b.verified_at,
|
|
verifiedAt: b.verified_at,
|
|
method: b.verification_method as any,
|
|
},
|
|
assignment: {
|
|
assignedTo: b.assigned_to || null,
|
|
},
|
|
notes: b.notes,
|
|
createdAt: b.created_at,
|
|
createdBy: 'system',
|
|
updatedAt: b.updated_at,
|
|
tenantId: b.tenant_id,
|
|
}
|
|
}
|
|
|
|
function getSdkHeaders(): HeadersInit {
|
|
if (typeof window === 'undefined') return {}
|
|
return {
|
|
'Content-Type': 'application/json',
|
|
'X-Tenant-ID': localStorage.getItem('bp_tenant_id') || '',
|
|
'X-User-ID': localStorage.getItem('bp_user_id') || '',
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetch DSR list from SDK backend via proxy
|
|
*/
|
|
export async function fetchSDKDSRList(): Promise<{ requests: DSRRequest[]; statistics: DSRStatistics }> {
|
|
const res = await fetch('/api/sdk/v1/dsgvo/dsr', {
|
|
headers: getSdkHeaders(),
|
|
})
|
|
if (!res.ok) {
|
|
throw new Error(`HTTP ${res.status}`)
|
|
}
|
|
const data = await res.json()
|
|
const backendDSRs: BackendDSR[] = data.dsrs || []
|
|
const requests = backendDSRs.map(transformBackendDSR)
|
|
|
|
// Calculate statistics locally
|
|
const now = new Date()
|
|
const statistics: DSRStatistics = {
|
|
total: requests.length,
|
|
byStatus: {
|
|
intake: requests.filter(r => r.status === 'intake').length,
|
|
identity_verification: requests.filter(r => r.status === 'identity_verification').length,
|
|
processing: requests.filter(r => r.status === 'processing').length,
|
|
completed: requests.filter(r => r.status === 'completed').length,
|
|
rejected: requests.filter(r => r.status === 'rejected').length,
|
|
cancelled: requests.filter(r => r.status === 'cancelled').length,
|
|
},
|
|
byType: {
|
|
access: requests.filter(r => r.type === 'access').length,
|
|
rectification: requests.filter(r => r.type === 'rectification').length,
|
|
erasure: requests.filter(r => r.type === 'erasure').length,
|
|
restriction: requests.filter(r => r.type === 'restriction').length,
|
|
portability: requests.filter(r => r.type === 'portability').length,
|
|
objection: requests.filter(r => r.type === 'objection').length,
|
|
},
|
|
overdue: requests.filter(r => {
|
|
if (r.status === 'completed' || r.status === 'rejected' || r.status === 'cancelled') return false
|
|
return new Date(r.deadline.currentDeadline) < now
|
|
}).length,
|
|
dueThisWeek: requests.filter(r => {
|
|
if (r.status === 'completed' || r.status === 'rejected' || r.status === 'cancelled') return false
|
|
const deadline = new Date(r.deadline.currentDeadline)
|
|
const weekFromNow = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000)
|
|
return deadline >= now && deadline <= weekFromNow
|
|
}).length,
|
|
averageProcessingDays: 0,
|
|
completedThisMonth: requests.filter(r => {
|
|
if (r.status !== 'completed' || !r.completedAt) return false
|
|
const completed = new Date(r.completedAt)
|
|
return completed.getMonth() === now.getMonth() && completed.getFullYear() === now.getFullYear()
|
|
}).length,
|
|
}
|
|
|
|
return { requests, statistics }
|
|
}
|
|
|
|
/**
|
|
* Create a new DSR via SDK backend
|
|
*/
|
|
export async function createSDKDSR(request: DSRCreateRequest): Promise<void> {
|
|
const body = {
|
|
request_type: request.type,
|
|
subject_name: request.requester.name,
|
|
subject_email: request.requester.email,
|
|
subject_identifier: request.requester.customerId || '',
|
|
request_description: request.requestText || '',
|
|
request_channel: request.source === 'web_form' ? 'form' : request.source,
|
|
notes: '',
|
|
}
|
|
const res = await fetch('/api/sdk/v1/dsgvo/dsr', {
|
|
method: 'POST',
|
|
headers: getSdkHeaders(),
|
|
body: JSON.stringify(body),
|
|
})
|
|
if (!res.ok) {
|
|
throw new Error(`HTTP ${res.status}`)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetch a single DSR by ID from SDK backend
|
|
*/
|
|
export async function fetchSDKDSR(id: string): Promise<DSRRequest | null> {
|
|
const res = await fetch(`/api/sdk/v1/dsgvo/dsr/${id}`, {
|
|
headers: getSdkHeaders(),
|
|
})
|
|
if (!res.ok) {
|
|
return null
|
|
}
|
|
const data = await res.json()
|
|
if (!data || !data.id) return null
|
|
return transformBackendDSR(data)
|
|
}
|
|
|
|
/**
|
|
* Update DSR status via SDK backend
|
|
*/
|
|
export async function updateSDKDSRStatus(id: string, status: string): Promise<void> {
|
|
const res = await fetch(`/api/sdk/v1/dsgvo/dsr/${id}`, {
|
|
method: 'PUT',
|
|
headers: getSdkHeaders(),
|
|
body: JSON.stringify({ status }),
|
|
})
|
|
if (!res.ok) {
|
|
throw new Error(`HTTP ${res.status}`)
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// MOCK DATA FUNCTIONS (kept as fallback)
|
|
// =============================================================================
|
|
|
|
export function createMockDSRList(): DSRRequest[] {
|
|
const now = new Date()
|
|
|
|
return [
|
|
{
|
|
id: 'dsr-001',
|
|
referenceNumber: 'DSR-2025-000001',
|
|
type: 'access',
|
|
status: 'intake',
|
|
priority: 'high',
|
|
requester: {
|
|
name: 'Max Mustermann',
|
|
email: 'max.mustermann@example.de'
|
|
},
|
|
source: 'web_form',
|
|
sourceDetails: 'Kontaktformular auf breakpilot.de',
|
|
receivedAt: new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000).toISOString(),
|
|
deadline: {
|
|
originalDeadline: new Date(now.getTime() + 28 * 24 * 60 * 60 * 1000).toISOString(),
|
|
currentDeadline: new Date(now.getTime() + 28 * 24 * 60 * 60 * 1000).toISOString(),
|
|
extended: false
|
|
},
|
|
identityVerification: {
|
|
verified: false
|
|
},
|
|
assignment: {
|
|
assignedTo: null
|
|
},
|
|
createdAt: new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000).toISOString(),
|
|
createdBy: 'system',
|
|
updatedAt: new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000).toISOString(),
|
|
tenantId: 'default-tenant'
|
|
},
|
|
{
|
|
id: 'dsr-002',
|
|
referenceNumber: 'DSR-2025-000002',
|
|
type: 'erasure',
|
|
status: 'identity_verification',
|
|
priority: 'high',
|
|
requester: {
|
|
name: 'Anna Schmidt',
|
|
email: 'anna.schmidt@example.de',
|
|
phone: '+49 170 1234567'
|
|
},
|
|
source: 'email',
|
|
requestText: 'Ich moechte, dass alle meine Daten geloescht werden.',
|
|
receivedAt: new Date(now.getTime() - 5 * 24 * 60 * 60 * 1000).toISOString(),
|
|
deadline: {
|
|
originalDeadline: new Date(now.getTime() + 9 * 24 * 60 * 60 * 1000).toISOString(),
|
|
currentDeadline: new Date(now.getTime() + 9 * 24 * 60 * 60 * 1000).toISOString(),
|
|
extended: false
|
|
},
|
|
identityVerification: {
|
|
verified: false
|
|
},
|
|
assignment: {
|
|
assignedTo: 'DSB Mueller',
|
|
assignedAt: new Date(now.getTime() - 4 * 24 * 60 * 60 * 1000).toISOString()
|
|
},
|
|
createdAt: new Date(now.getTime() - 5 * 24 * 60 * 60 * 1000).toISOString(),
|
|
createdBy: 'system',
|
|
updatedAt: new Date(now.getTime() - 4 * 24 * 60 * 60 * 1000).toISOString(),
|
|
tenantId: 'default-tenant'
|
|
},
|
|
{
|
|
id: 'dsr-003',
|
|
referenceNumber: 'DSR-2025-000003',
|
|
type: 'rectification',
|
|
status: 'processing',
|
|
priority: 'normal',
|
|
requester: {
|
|
name: 'Peter Meier',
|
|
email: 'peter.meier@example.de'
|
|
},
|
|
source: 'email',
|
|
requestText: 'Meine Adresse ist falsch gespeichert.',
|
|
receivedAt: new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString(),
|
|
deadline: {
|
|
originalDeadline: new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000).toISOString(),
|
|
currentDeadline: new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000).toISOString(),
|
|
extended: false
|
|
},
|
|
identityVerification: {
|
|
verified: true,
|
|
method: 'existing_account',
|
|
verifiedAt: new Date(now.getTime() - 6 * 24 * 60 * 60 * 1000).toISOString(),
|
|
verifiedBy: 'DSB Mueller'
|
|
},
|
|
assignment: {
|
|
assignedTo: 'DSB Mueller',
|
|
assignedAt: new Date(now.getTime() - 6 * 24 * 60 * 60 * 1000).toISOString()
|
|
},
|
|
rectificationDetails: {
|
|
fieldsToCorrect: [
|
|
{
|
|
field: 'Adresse',
|
|
currentValue: 'Musterstr. 1, 12345 Berlin',
|
|
requestedValue: 'Musterstr. 10, 12345 Berlin',
|
|
corrected: false
|
|
}
|
|
]
|
|
},
|
|
createdAt: new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString(),
|
|
createdBy: 'system',
|
|
updatedAt: new Date(now.getTime() - 1 * 24 * 60 * 60 * 1000).toISOString(),
|
|
tenantId: 'default-tenant'
|
|
},
|
|
{
|
|
id: 'dsr-004',
|
|
referenceNumber: 'DSR-2025-000004',
|
|
type: 'portability',
|
|
status: 'processing',
|
|
priority: 'normal',
|
|
requester: {
|
|
name: 'Lisa Weber',
|
|
email: 'lisa.weber@example.de'
|
|
},
|
|
source: 'web_form',
|
|
receivedAt: new Date(now.getTime() - 10 * 24 * 60 * 60 * 1000).toISOString(),
|
|
deadline: {
|
|
originalDeadline: new Date(now.getTime() + 20 * 24 * 60 * 60 * 1000).toISOString(),
|
|
currentDeadline: new Date(now.getTime() + 20 * 24 * 60 * 60 * 1000).toISOString(),
|
|
extended: false
|
|
},
|
|
identityVerification: {
|
|
verified: true,
|
|
method: 'id_document',
|
|
verifiedAt: new Date(now.getTime() - 8 * 24 * 60 * 60 * 1000).toISOString(),
|
|
verifiedBy: 'DSB Mueller'
|
|
},
|
|
assignment: {
|
|
assignedTo: 'IT Team',
|
|
assignedAt: new Date(now.getTime() - 8 * 24 * 60 * 60 * 1000).toISOString()
|
|
},
|
|
notes: 'JSON-Export wird vorbereitet',
|
|
createdAt: new Date(now.getTime() - 10 * 24 * 60 * 60 * 1000).toISOString(),
|
|
createdBy: 'system',
|
|
updatedAt: new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000).toISOString(),
|
|
tenantId: 'default-tenant'
|
|
},
|
|
{
|
|
id: 'dsr-005',
|
|
referenceNumber: 'DSR-2025-000005',
|
|
type: 'objection',
|
|
status: 'rejected',
|
|
priority: 'low',
|
|
requester: {
|
|
name: 'Thomas Klein',
|
|
email: 'thomas.klein@example.de'
|
|
},
|
|
source: 'letter',
|
|
requestText: 'Ich widerspreche der Verarbeitung meiner Daten fuer Marketingzwecke.',
|
|
receivedAt: new Date(now.getTime() - 35 * 24 * 60 * 60 * 1000).toISOString(),
|
|
deadline: {
|
|
originalDeadline: new Date(now.getTime() - 5 * 24 * 60 * 60 * 1000).toISOString(),
|
|
currentDeadline: new Date(now.getTime() - 5 * 24 * 60 * 60 * 1000).toISOString(),
|
|
extended: false
|
|
},
|
|
completedAt: new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString(),
|
|
identityVerification: {
|
|
verified: true,
|
|
method: 'postal',
|
|
verifiedAt: new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000).toISOString(),
|
|
verifiedBy: 'DSB Mueller'
|
|
},
|
|
assignment: {
|
|
assignedTo: 'Rechtsabteilung',
|
|
assignedAt: new Date(now.getTime() - 28 * 24 * 60 * 60 * 1000).toISOString()
|
|
},
|
|
objectionDetails: {
|
|
processingPurpose: 'Marketing',
|
|
legalBasis: 'Berechtigtes Interesse (Art. 6(1)(f))',
|
|
objectionGrounds: 'Keine konkreten Gruende genannt',
|
|
decision: 'rejected',
|
|
decisionReason: 'Zwingende schutzwuerdige Gruende fuer die Verarbeitung ueberwiegen',
|
|
decisionBy: 'Rechtsabteilung',
|
|
decisionAt: new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString()
|
|
},
|
|
notes: 'Widerspruch unberechtigt - zwingende schutzwuerdige Gruende',
|
|
createdAt: new Date(now.getTime() - 35 * 24 * 60 * 60 * 1000).toISOString(),
|
|
createdBy: 'system',
|
|
updatedAt: new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString(),
|
|
tenantId: 'default-tenant'
|
|
},
|
|
{
|
|
id: 'dsr-006',
|
|
referenceNumber: 'DSR-2025-000006',
|
|
type: 'access',
|
|
status: 'completed',
|
|
priority: 'normal',
|
|
requester: {
|
|
name: 'Sarah Braun',
|
|
email: 'sarah.braun@example.de'
|
|
},
|
|
source: 'email',
|
|
receivedAt: new Date(now.getTime() - 45 * 24 * 60 * 60 * 1000).toISOString(),
|
|
deadline: {
|
|
originalDeadline: new Date(now.getTime() - 15 * 24 * 60 * 60 * 1000).toISOString(),
|
|
currentDeadline: new Date(now.getTime() - 15 * 24 * 60 * 60 * 1000).toISOString(),
|
|
extended: false
|
|
},
|
|
completedAt: new Date(now.getTime() - 20 * 24 * 60 * 60 * 1000).toISOString(),
|
|
identityVerification: {
|
|
verified: true,
|
|
method: 'id_document',
|
|
verifiedAt: new Date(now.getTime() - 42 * 24 * 60 * 60 * 1000).toISOString(),
|
|
verifiedBy: 'DSB Mueller'
|
|
},
|
|
assignment: {
|
|
assignedTo: 'DSB Mueller',
|
|
assignedAt: new Date(now.getTime() - 42 * 24 * 60 * 60 * 1000).toISOString()
|
|
},
|
|
dataExport: {
|
|
format: 'pdf',
|
|
generatedAt: new Date(now.getTime() - 20 * 24 * 60 * 60 * 1000).toISOString(),
|
|
generatedBy: 'DSB Mueller',
|
|
fileName: 'datenauskunft_sarah_braun.pdf',
|
|
fileSize: 245000,
|
|
includesThirdPartyData: false
|
|
},
|
|
createdAt: new Date(now.getTime() - 45 * 24 * 60 * 60 * 1000).toISOString(),
|
|
createdBy: 'system',
|
|
updatedAt: new Date(now.getTime() - 20 * 24 * 60 * 60 * 1000).toISOString(),
|
|
tenantId: 'default-tenant'
|
|
}
|
|
]
|
|
}
|
|
|
|
export function createMockStatistics(): DSRStatistics {
|
|
return {
|
|
total: 6,
|
|
byStatus: {
|
|
intake: 1,
|
|
identity_verification: 1,
|
|
processing: 2,
|
|
completed: 1,
|
|
rejected: 1,
|
|
cancelled: 0
|
|
},
|
|
byType: {
|
|
access: 2,
|
|
rectification: 1,
|
|
erasure: 1,
|
|
restriction: 0,
|
|
portability: 1,
|
|
objection: 1
|
|
},
|
|
overdue: 0,
|
|
dueThisWeek: 2,
|
|
averageProcessingDays: 18,
|
|
completedThisMonth: 1
|
|
}
|
|
}
|