fix(admin-v2): Restore complete admin-v2 application
The admin-v2 application was incomplete in the repository. This commit restores all missing components: - Admin pages (76 pages): dashboard, ai, compliance, dsgvo, education, infrastructure, communication, development, onboarding, rbac - SDK pages (45 pages): tom, dsfa, vvt, loeschfristen, einwilligungen, vendor-compliance, tom-generator, dsr, and more - Developer portal (25 pages): API docs, SDK guides, frameworks - All components, lib files, hooks, and types - Updated package.json with all dependencies The issue was caused by incomplete initial repository state - the full admin-v2 codebase existed in backend/admin-v2 and docs-src/admin-v2 but was never fully synced to the main admin-v2 directory. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
664
admin-v2/lib/sdk/dsr/api.ts
Normal file
664
admin-v2/lib/sdk/dsr/api.ts
Normal file
@@ -0,0 +1,664 @@
|
||||
/**
|
||||
* 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 })
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// MOCK DATA FUNCTIONS (for development without backend)
|
||||
// =============================================================================
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
6
admin-v2/lib/sdk/dsr/index.ts
Normal file
6
admin-v2/lib/sdk/dsr/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* DSR Module Exports
|
||||
*/
|
||||
|
||||
export * from './types'
|
||||
export * from './api'
|
||||
581
admin-v2/lib/sdk/dsr/types.ts
Normal file
581
admin-v2/lib/sdk/dsr/types.ts
Normal file
@@ -0,0 +1,581 @@
|
||||
/**
|
||||
* DSR (Data Subject Request) Types
|
||||
*
|
||||
* TypeScript definitions for GDPR Art. 15-21 Data Subject Requests
|
||||
* Based on the Go Consent Service backend API structure
|
||||
*/
|
||||
|
||||
// =============================================================================
|
||||
// ENUMS & CONSTANTS
|
||||
// =============================================================================
|
||||
|
||||
export type DSRType =
|
||||
| 'access' // Art. 15 - Auskunftsrecht
|
||||
| 'rectification' // Art. 16 - Berichtigungsrecht
|
||||
| 'erasure' // Art. 17 - Loeschungsrecht
|
||||
| 'restriction' // Art. 18 - Einschraenkungsrecht
|
||||
| 'portability' // Art. 20 - Datenuebertragbarkeit
|
||||
| 'objection' // Art. 21 - Widerspruchsrecht
|
||||
|
||||
export type DSRStatus =
|
||||
| 'intake' // Eingang - Anfrage dokumentiert
|
||||
| 'identity_verification' // Identitaetspruefung
|
||||
| 'processing' // In Bearbeitung
|
||||
| 'completed' // Abgeschlossen
|
||||
| 'rejected' // Abgelehnt
|
||||
| 'cancelled' // Storniert
|
||||
|
||||
export type DSRPriority = 'low' | 'normal' | 'high' | 'critical'
|
||||
|
||||
export type DSRSource =
|
||||
| 'web_form' // Kontaktformular/Portal
|
||||
| 'email' // E-Mail
|
||||
| 'letter' // Brief
|
||||
| 'phone' // Telefon
|
||||
| 'in_person' // Persoenlich
|
||||
| 'other' // Sonstiges
|
||||
|
||||
export type IdentityVerificationMethod =
|
||||
| 'id_document' // Ausweiskopie
|
||||
| 'email' // E-Mail-Bestaetigung
|
||||
| 'phone' // Telefonische Bestaetigung
|
||||
| 'postal' // Postalische Bestaetigung
|
||||
| 'existing_account' // Bestehendes Kundenkonto
|
||||
| 'other' // Sonstiges
|
||||
|
||||
export type CommunicationType =
|
||||
| 'incoming' // Eingehend (vom Betroffenen)
|
||||
| 'outgoing' // Ausgehend (an Betroffenen)
|
||||
| 'internal' // Intern (Notizen)
|
||||
|
||||
export type CommunicationChannel =
|
||||
| 'email'
|
||||
| 'letter'
|
||||
| 'phone'
|
||||
| 'portal'
|
||||
| 'internal_note'
|
||||
|
||||
// =============================================================================
|
||||
// DSR TYPE METADATA
|
||||
// =============================================================================
|
||||
|
||||
export interface DSRTypeInfo {
|
||||
type: DSRType
|
||||
article: string
|
||||
label: string
|
||||
labelShort: string
|
||||
description: string
|
||||
defaultDeadlineDays: number
|
||||
maxExtensionMonths: number
|
||||
color: string
|
||||
bgColor: string
|
||||
processDocument?: string // Reference to process document
|
||||
}
|
||||
|
||||
export const DSR_TYPE_INFO: Record<DSRType, DSRTypeInfo> = {
|
||||
access: {
|
||||
type: 'access',
|
||||
article: 'Art. 15',
|
||||
label: 'Auskunftsrecht',
|
||||
labelShort: 'Auskunft',
|
||||
description: 'Recht auf Auskunft ueber gespeicherte personenbezogene Daten',
|
||||
defaultDeadlineDays: 30,
|
||||
maxExtensionMonths: 2,
|
||||
color: 'text-blue-700',
|
||||
bgColor: 'bg-blue-100',
|
||||
processDocument: 'Prozessbeschreibung Art. 15 DSGVO_v02.pdf'
|
||||
},
|
||||
rectification: {
|
||||
type: 'rectification',
|
||||
article: 'Art. 16',
|
||||
label: 'Berichtigungsrecht',
|
||||
labelShort: 'Berichtigung',
|
||||
description: 'Recht auf Berichtigung unrichtiger personenbezogener Daten',
|
||||
defaultDeadlineDays: 14,
|
||||
maxExtensionMonths: 2,
|
||||
color: 'text-yellow-700',
|
||||
bgColor: 'bg-yellow-100',
|
||||
processDocument: 'Prozessbeschriebung Art. 16 DSGVO_v02.pdf'
|
||||
},
|
||||
erasure: {
|
||||
type: 'erasure',
|
||||
article: 'Art. 17',
|
||||
label: 'Loeschungsrecht',
|
||||
labelShort: 'Loeschung',
|
||||
description: 'Recht auf Loeschung personenbezogener Daten ("Recht auf Vergessenwerden")',
|
||||
defaultDeadlineDays: 14,
|
||||
maxExtensionMonths: 2,
|
||||
color: 'text-red-700',
|
||||
bgColor: 'bg-red-100',
|
||||
processDocument: 'Prozessbeschreibung Art. 17 DSGVO_v03.pdf'
|
||||
},
|
||||
restriction: {
|
||||
type: 'restriction',
|
||||
article: 'Art. 18',
|
||||
label: 'Einschraenkungsrecht',
|
||||
labelShort: 'Einschraenkung',
|
||||
description: 'Recht auf Einschraenkung der Verarbeitung',
|
||||
defaultDeadlineDays: 14,
|
||||
maxExtensionMonths: 2,
|
||||
color: 'text-orange-700',
|
||||
bgColor: 'bg-orange-100',
|
||||
processDocument: 'Prozessbeschreibung Art. 18 DSGVO_v01.pdf'
|
||||
},
|
||||
portability: {
|
||||
type: 'portability',
|
||||
article: 'Art. 20',
|
||||
label: 'Datenuebertragbarkeit',
|
||||
labelShort: 'Uebertragung',
|
||||
description: 'Recht auf Datenuebertragbarkeit in maschinenlesbarem Format',
|
||||
defaultDeadlineDays: 30,
|
||||
maxExtensionMonths: 2,
|
||||
color: 'text-purple-700',
|
||||
bgColor: 'bg-purple-100',
|
||||
processDocument: 'Prozessbeschreibung Art. 20 DSGVO_v02.pdf'
|
||||
},
|
||||
objection: {
|
||||
type: 'objection',
|
||||
article: 'Art. 21',
|
||||
label: 'Widerspruchsrecht',
|
||||
labelShort: 'Widerspruch',
|
||||
description: 'Recht auf Widerspruch gegen die Verarbeitung',
|
||||
defaultDeadlineDays: 30,
|
||||
maxExtensionMonths: 0, // No extension allowed for objections
|
||||
color: 'text-gray-700',
|
||||
bgColor: 'bg-gray-100'
|
||||
}
|
||||
}
|
||||
|
||||
export const DSR_STATUS_INFO: Record<DSRStatus, { label: string; color: string; bgColor: string; borderColor: string }> = {
|
||||
intake: {
|
||||
label: 'Eingang',
|
||||
color: 'text-blue-700',
|
||||
bgColor: 'bg-blue-100',
|
||||
borderColor: 'border-blue-200'
|
||||
},
|
||||
identity_verification: {
|
||||
label: 'ID-Pruefung',
|
||||
color: 'text-yellow-700',
|
||||
bgColor: 'bg-yellow-100',
|
||||
borderColor: 'border-yellow-200'
|
||||
},
|
||||
processing: {
|
||||
label: 'In Bearbeitung',
|
||||
color: 'text-purple-700',
|
||||
bgColor: 'bg-purple-100',
|
||||
borderColor: 'border-purple-200'
|
||||
},
|
||||
completed: {
|
||||
label: 'Abgeschlossen',
|
||||
color: 'text-green-700',
|
||||
bgColor: 'bg-green-100',
|
||||
borderColor: 'border-green-200'
|
||||
},
|
||||
rejected: {
|
||||
label: 'Abgelehnt',
|
||||
color: 'text-red-700',
|
||||
bgColor: 'bg-red-100',
|
||||
borderColor: 'border-red-200'
|
||||
},
|
||||
cancelled: {
|
||||
label: 'Storniert',
|
||||
color: 'text-gray-700',
|
||||
bgColor: 'bg-gray-100',
|
||||
borderColor: 'border-gray-200'
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// MAIN INTERFACES
|
||||
// =============================================================================
|
||||
|
||||
export interface DSRRequester {
|
||||
name: string
|
||||
email: string
|
||||
phone?: string
|
||||
address?: string
|
||||
customerId?: string // If existing customer
|
||||
}
|
||||
|
||||
export interface DSRIdentityVerification {
|
||||
verified: boolean
|
||||
method?: IdentityVerificationMethod
|
||||
verifiedAt?: string
|
||||
verifiedBy?: string
|
||||
notes?: string
|
||||
documentRef?: string // Reference to uploaded ID document
|
||||
}
|
||||
|
||||
export interface DSRAssignment {
|
||||
assignedTo: string | null
|
||||
assignedAt?: string
|
||||
assignedBy?: string
|
||||
}
|
||||
|
||||
export interface DSRDeadline {
|
||||
originalDeadline: string
|
||||
currentDeadline: string
|
||||
extended: boolean
|
||||
extensionReason?: string
|
||||
extensionApprovedBy?: string
|
||||
extensionApprovedAt?: string
|
||||
}
|
||||
|
||||
export interface DSRRequest {
|
||||
id: string
|
||||
referenceNumber: string // e.g., "DSR-2025-000042"
|
||||
type: DSRType
|
||||
status: DSRStatus
|
||||
priority: DSRPriority
|
||||
|
||||
// Requester info
|
||||
requester: DSRRequester
|
||||
|
||||
// Request details
|
||||
source: DSRSource
|
||||
sourceDetails?: string // e.g., "Kontaktformular auf website.de"
|
||||
requestText?: string // Original request text
|
||||
|
||||
// Dates
|
||||
receivedAt: string
|
||||
deadline: DSRDeadline
|
||||
completedAt?: string
|
||||
|
||||
// Verification
|
||||
identityVerification: DSRIdentityVerification
|
||||
|
||||
// Assignment
|
||||
assignment: DSRAssignment
|
||||
|
||||
// Processing
|
||||
notes?: string
|
||||
internalNotes?: string
|
||||
|
||||
// Type-specific data
|
||||
erasureChecklist?: DSRErasureChecklist // For Art. 17
|
||||
dataExport?: DSRDataExport // For Art. 15, 20
|
||||
rectificationDetails?: DSRRectificationDetails // For Art. 16
|
||||
objectionDetails?: DSRObjectionDetails // For Art. 21
|
||||
|
||||
// Audit
|
||||
createdAt: string
|
||||
createdBy: string
|
||||
updatedAt: string
|
||||
updatedBy?: string
|
||||
|
||||
// Metadata
|
||||
tenantId: string
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// TYPE-SPECIFIC INTERFACES
|
||||
// =============================================================================
|
||||
|
||||
// Art. 17(3) Erasure Exceptions Checklist
|
||||
export interface DSRErasureChecklistItem {
|
||||
id: string
|
||||
article: string // e.g., "17(3)(a)"
|
||||
label: string
|
||||
description: string
|
||||
checked: boolean
|
||||
applies: boolean
|
||||
notes?: string
|
||||
}
|
||||
|
||||
export interface DSRErasureChecklist {
|
||||
items: DSRErasureChecklistItem[]
|
||||
canProceedWithErasure: boolean
|
||||
reviewedBy?: string
|
||||
reviewedAt?: string
|
||||
}
|
||||
|
||||
export const ERASURE_EXCEPTIONS: Omit<DSRErasureChecklistItem, 'checked' | 'applies' | 'notes'>[] = [
|
||||
{
|
||||
id: 'art17_3_a',
|
||||
article: '17(3)(a)',
|
||||
label: 'Meinungs- und Informationsfreiheit',
|
||||
description: 'Ausuebung des Rechts auf freie Meinungsaeusserung und Information'
|
||||
},
|
||||
{
|
||||
id: 'art17_3_b',
|
||||
article: '17(3)(b)',
|
||||
label: 'Rechtliche Verpflichtung',
|
||||
description: 'Erfuellung einer rechtlichen Verpflichtung (z.B. Aufbewahrungspflichten)'
|
||||
},
|
||||
{
|
||||
id: 'art17_3_c',
|
||||
article: '17(3)(c)',
|
||||
label: 'Oeffentliches Interesse',
|
||||
description: 'Gruende des oeffentlichen Interesses im Bereich Gesundheit'
|
||||
},
|
||||
{
|
||||
id: 'art17_3_d',
|
||||
article: '17(3)(d)',
|
||||
label: 'Archivzwecke',
|
||||
description: 'Archivzwecke, wissenschaftliche/historische Forschung, Statistik'
|
||||
},
|
||||
{
|
||||
id: 'art17_3_e',
|
||||
article: '17(3)(e)',
|
||||
label: 'Rechtsansprueche',
|
||||
description: 'Geltendmachung, Ausuebung oder Verteidigung von Rechtsanspruechen'
|
||||
}
|
||||
]
|
||||
|
||||
// Data Export for Art. 15, 20
|
||||
export interface DSRDataExport {
|
||||
format: 'json' | 'csv' | 'xml' | 'pdf'
|
||||
generatedAt?: string
|
||||
generatedBy?: string
|
||||
fileUrl?: string
|
||||
fileName?: string
|
||||
fileSize?: number
|
||||
includesThirdPartyData: boolean
|
||||
anonymizedFields?: string[]
|
||||
transferMethod?: 'download' | 'email' | 'third_party' // For Art. 20 transfer
|
||||
transferRecipient?: string // For Art. 20 transfer to another controller
|
||||
}
|
||||
|
||||
// Rectification Details for Art. 16
|
||||
export interface DSRRectificationDetails {
|
||||
fieldsToCorrect: {
|
||||
field: string
|
||||
currentValue: string
|
||||
requestedValue: string
|
||||
corrected: boolean
|
||||
correctedAt?: string
|
||||
correctedBy?: string
|
||||
}[]
|
||||
}
|
||||
|
||||
// Objection Details for Art. 21
|
||||
export interface DSRObjectionDetails {
|
||||
processingPurpose: string
|
||||
legalBasis: string
|
||||
objectionGrounds: string
|
||||
decision: 'accepted' | 'rejected' | 'pending'
|
||||
decisionReason?: string
|
||||
decisionBy?: string
|
||||
decisionAt?: string
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// COMMUNICATION
|
||||
// =============================================================================
|
||||
|
||||
export interface DSRCommunication {
|
||||
id: string
|
||||
dsrId: string
|
||||
type: CommunicationType
|
||||
channel: CommunicationChannel
|
||||
subject?: string
|
||||
content: string
|
||||
templateUsed?: string // Reference to email template
|
||||
attachments?: {
|
||||
name: string
|
||||
url: string
|
||||
size: number
|
||||
type: string
|
||||
}[]
|
||||
sentAt?: string
|
||||
sentBy?: string
|
||||
receivedAt?: string
|
||||
createdAt: string
|
||||
createdBy: string
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// AUDIT LOG
|
||||
// =============================================================================
|
||||
|
||||
export interface DSRAuditEntry {
|
||||
id: string
|
||||
dsrId: string
|
||||
action: string // e.g., "status_changed", "identity_verified", "assigned"
|
||||
previousValue?: string
|
||||
newValue?: string
|
||||
performedBy: string
|
||||
performedAt: string
|
||||
notes?: string
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// EMAIL TEMPLATES
|
||||
// =============================================================================
|
||||
|
||||
export interface DSREmailTemplate {
|
||||
id: string
|
||||
name: string
|
||||
subject: string
|
||||
body: string
|
||||
type: DSRType | 'general'
|
||||
stage: DSRStatus | 'identity_request' | 'deadline_extension' | 'completion'
|
||||
language: 'de' | 'en'
|
||||
variables: string[] // e.g., ["requesterName", "referenceNumber", "deadline"]
|
||||
}
|
||||
|
||||
export const DSR_EMAIL_TEMPLATES: DSREmailTemplate[] = [
|
||||
{
|
||||
id: 'intake_confirmation',
|
||||
name: 'Eingangsbestaetigung',
|
||||
subject: 'Bestaetigung Ihrer Anfrage - {{referenceNumber}}',
|
||||
body: `Sehr geehrte(r) {{requesterName}},
|
||||
|
||||
wir bestaetigen den Eingang Ihrer Anfrage vom {{receivedDate}}.
|
||||
|
||||
Referenznummer: {{referenceNumber}}
|
||||
Art der Anfrage: {{requestType}}
|
||||
|
||||
Wir werden Ihre Anfrage innerhalb der gesetzlichen Frist von {{deadline}} bearbeiten.
|
||||
|
||||
Mit freundlichen Gruessen
|
||||
{{senderName}}
|
||||
Datenschutzbeauftragter`,
|
||||
type: 'general',
|
||||
stage: 'intake',
|
||||
language: 'de',
|
||||
variables: ['requesterName', 'receivedDate', 'referenceNumber', 'requestType', 'deadline', 'senderName']
|
||||
},
|
||||
{
|
||||
id: 'identity_request',
|
||||
name: 'Identitaetsanfrage',
|
||||
subject: 'Identitaetspruefung erforderlich - {{referenceNumber}}',
|
||||
body: `Sehr geehrte(r) {{requesterName}},
|
||||
|
||||
um Ihre Anfrage bearbeiten zu koennen, benoetigen wir einen Nachweis Ihrer Identitaet.
|
||||
|
||||
Bitte senden Sie uns eines der folgenden Dokumente:
|
||||
- Kopie Ihres Personalausweises (Vorder- und Rueckseite)
|
||||
- Kopie Ihres Reisepasses
|
||||
|
||||
Ihre Daten werden ausschliesslich zur Identitaetspruefung verwendet und anschliessend geloescht.
|
||||
|
||||
Mit freundlichen Gruessen
|
||||
{{senderName}}
|
||||
Datenschutzbeauftragter`,
|
||||
type: 'general',
|
||||
stage: 'identity_request',
|
||||
language: 'de',
|
||||
variables: ['requesterName', 'referenceNumber', 'senderName']
|
||||
}
|
||||
]
|
||||
|
||||
// =============================================================================
|
||||
// API TYPES
|
||||
// =============================================================================
|
||||
|
||||
export interface DSRFilters {
|
||||
status?: DSRStatus | DSRStatus[]
|
||||
type?: DSRType | DSRType[]
|
||||
priority?: DSRPriority
|
||||
assignedTo?: string
|
||||
overdue?: boolean
|
||||
search?: string
|
||||
dateFrom?: string
|
||||
dateTo?: string
|
||||
}
|
||||
|
||||
export interface DSRListResponse {
|
||||
requests: DSRRequest[]
|
||||
total: number
|
||||
page: number
|
||||
pageSize: number
|
||||
}
|
||||
|
||||
export interface DSRCreateRequest {
|
||||
type: DSRType
|
||||
requester: DSRRequester
|
||||
source: DSRSource
|
||||
sourceDetails?: string
|
||||
requestText?: string
|
||||
priority?: DSRPriority
|
||||
}
|
||||
|
||||
export interface DSRUpdateRequest {
|
||||
status?: DSRStatus
|
||||
priority?: DSRPriority
|
||||
notes?: string
|
||||
internalNotes?: string
|
||||
assignment?: DSRAssignment
|
||||
}
|
||||
|
||||
export interface DSRVerifyIdentityRequest {
|
||||
method: IdentityVerificationMethod
|
||||
notes?: string
|
||||
documentRef?: string
|
||||
}
|
||||
|
||||
export interface DSRCompleteRequest {
|
||||
completionNotes?: string
|
||||
dataExport?: DSRDataExport
|
||||
}
|
||||
|
||||
export interface DSRRejectRequest {
|
||||
reason: string
|
||||
legalBasis?: string // e.g., Art. 17(3) exception
|
||||
}
|
||||
|
||||
export interface DSRExtendDeadlineRequest {
|
||||
extensionMonths: 1 | 2
|
||||
reason: string
|
||||
}
|
||||
|
||||
export interface DSRSendCommunicationRequest {
|
||||
type: CommunicationType
|
||||
channel: CommunicationChannel
|
||||
subject?: string
|
||||
content: string
|
||||
templateId?: string
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// STATISTICS
|
||||
// =============================================================================
|
||||
|
||||
export interface DSRStatistics {
|
||||
total: number
|
||||
byStatus: Record<DSRStatus, number>
|
||||
byType: Record<DSRType, number>
|
||||
overdue: number
|
||||
dueThisWeek: number
|
||||
averageProcessingDays: number
|
||||
completedThisMonth: number
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// HELPER FUNCTIONS
|
||||
// =============================================================================
|
||||
|
||||
export function getDaysRemaining(deadline: string): number {
|
||||
const deadlineDate = new Date(deadline)
|
||||
const now = new Date()
|
||||
const diff = deadlineDate.getTime() - now.getTime()
|
||||
return Math.ceil(diff / (1000 * 60 * 60 * 24))
|
||||
}
|
||||
|
||||
export function isOverdue(request: DSRRequest): boolean {
|
||||
if (request.status === 'completed' || request.status === 'rejected' || request.status === 'cancelled') {
|
||||
return false
|
||||
}
|
||||
return getDaysRemaining(request.deadline.currentDeadline) < 0
|
||||
}
|
||||
|
||||
export function isUrgent(request: DSRRequest, thresholdDays: number = 7): boolean {
|
||||
if (request.status === 'completed' || request.status === 'rejected' || request.status === 'cancelled') {
|
||||
return false
|
||||
}
|
||||
const daysRemaining = getDaysRemaining(request.deadline.currentDeadline)
|
||||
return daysRemaining >= 0 && daysRemaining <= thresholdDays
|
||||
}
|
||||
|
||||
export function generateReferenceNumber(year: number, sequence: number): string {
|
||||
return `DSR-${year}-${String(sequence).padStart(6, '0')}`
|
||||
}
|
||||
|
||||
export function getTypeInfo(type: DSRType): DSRTypeInfo {
|
||||
return DSR_TYPE_INFO[type]
|
||||
}
|
||||
|
||||
export function getStatusInfo(status: DSRStatus) {
|
||||
return DSR_STATUS_INFO[status]
|
||||
}
|
||||
Reference in New Issue
Block a user