refactor(admin): split 8 oversized lib/ files into focused modules under 500 LOC
Split these files that exceeded the 500-line hard cap: - privacy-policy.ts (965 LOC) -> sections + renderers - academy/api.ts (787 LOC) -> courses + mock-data - whistleblower/api.ts (755 LOC) -> operations + mock-data - vvt-profiling.ts (659 LOC) -> data + logic - cookie-banner.ts (595 LOC) -> config + embed - dsr/types.ts (581 LOC) -> core + api types - tom-generator/rules-engine.ts (560 LOC) -> evaluator + gap-analysis - datapoint-helpers.ts (548 LOC) -> generators + validators Each original file becomes a barrel re-export for backward compatibility. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,755 +1,31 @@
|
||||
/**
|
||||
* Whistleblower System API Client
|
||||
* Whistleblower System API Client — barrel re-export
|
||||
*
|
||||
* API client for Hinweisgeberschutzgesetz (HinSchG) compliant
|
||||
* Whistleblower/Hinweisgebersystem management
|
||||
* Connects to the ai-compliance-sdk backend
|
||||
* Split into:
|
||||
* - api-operations.ts (CRUD, workflow, messaging, attachments, statistics)
|
||||
* - api-mock-data.ts (mock data + SDK proxy)
|
||||
*/
|
||||
|
||||
import {
|
||||
WhistleblowerReport,
|
||||
WhistleblowerStatistics,
|
||||
ReportListResponse,
|
||||
ReportFilters,
|
||||
PublicReportSubmission,
|
||||
ReportUpdateRequest,
|
||||
MessageSendRequest,
|
||||
AnonymousMessage,
|
||||
WhistleblowerMeasure,
|
||||
FileAttachment,
|
||||
ReportCategory,
|
||||
ReportStatus,
|
||||
ReportPriority,
|
||||
generateAccessKey
|
||||
} from './types'
|
||||
|
||||
// =============================================================================
|
||||
// CONFIGURATION
|
||||
// =============================================================================
|
||||
|
||||
const WB_API_BASE = process.env.NEXT_PUBLIC_SDK_API_URL || 'http://localhost:8093'
|
||||
const API_TIMEOUT = 30000 // 30 seconds
|
||||
|
||||
// =============================================================================
|
||||
// HELPER FUNCTIONS
|
||||
// =============================================================================
|
||||
|
||||
function getTenantId(): string {
|
||||
if (typeof window !== 'undefined') {
|
||||
return localStorage.getItem('bp_tenant_id') || 'default-tenant'
|
||||
}
|
||||
return 'default-tenant'
|
||||
}
|
||||
|
||||
function getAuthHeaders(): HeadersInit {
|
||||
const headers: HeadersInit = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Tenant-ID': getTenantId()
|
||||
}
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
const token = localStorage.getItem('authToken')
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`
|
||||
}
|
||||
const userId = localStorage.getItem('bp_user_id')
|
||||
if (userId) {
|
||||
headers['X-User-ID'] = userId
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ADMIN CRUD - Reports
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Alle Meldungen abrufen (Admin)
|
||||
*/
|
||||
export async function fetchReports(filters?: ReportFilters): Promise<ReportListResponse> {
|
||||
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.category) {
|
||||
const categories = Array.isArray(filters.category) ? filters.category : [filters.category]
|
||||
categories.forEach(c => params.append('category', c))
|
||||
}
|
||||
if (filters.priority) params.set('priority', filters.priority)
|
||||
if (filters.assignedTo) params.set('assignedTo', filters.assignedTo)
|
||||
if (filters.isAnonymous !== undefined) params.set('isAnonymous', String(filters.isAnonymous))
|
||||
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 = `${WB_API_BASE}/api/v1/admin/whistleblower/reports${queryString ? `?${queryString}` : ''}`
|
||||
|
||||
return fetchWithTimeout<ReportListResponse>(url)
|
||||
}
|
||||
|
||||
/**
|
||||
* Einzelne Meldung abrufen (Admin)
|
||||
*/
|
||||
export async function fetchReport(id: string): Promise<WhistleblowerReport> {
|
||||
return fetchWithTimeout<WhistleblowerReport>(
|
||||
`${WB_API_BASE}/api/v1/admin/whistleblower/reports/${id}`
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Meldung aktualisieren (Status, Prioritaet, Kategorie, Zuweisung)
|
||||
*/
|
||||
export async function updateReport(id: string, update: ReportUpdateRequest): Promise<WhistleblowerReport> {
|
||||
return fetchWithTimeout<WhistleblowerReport>(
|
||||
`${WB_API_BASE}/api/v1/admin/whistleblower/reports/${id}`,
|
||||
{
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(update)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Meldung loeschen (soft delete)
|
||||
*/
|
||||
export async function deleteReport(id: string): Promise<void> {
|
||||
await fetchWithTimeout<void>(
|
||||
`${WB_API_BASE}/api/v1/admin/whistleblower/reports/${id}`,
|
||||
{
|
||||
method: 'DELETE'
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// PUBLIC ENDPOINTS - Kein Auth erforderlich
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Neue Meldung einreichen (oeffentlich, keine Auth)
|
||||
*/
|
||||
export async function submitPublicReport(
|
||||
data: PublicReportSubmission
|
||||
): Promise<{ report: WhistleblowerReport; accessKey: string }> {
|
||||
const response = await fetch(
|
||||
`${WB_API_BASE}/api/v1/public/whistleblower/submit`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data)
|
||||
}
|
||||
)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
|
||||
}
|
||||
|
||||
return response.json()
|
||||
}
|
||||
|
||||
/**
|
||||
* Meldung ueber Zugangscode abrufen (oeffentlich, keine Auth)
|
||||
*/
|
||||
export async function fetchReportByAccessKey(
|
||||
accessKey: string
|
||||
): Promise<WhistleblowerReport> {
|
||||
const response = await fetch(
|
||||
`${WB_API_BASE}/api/v1/public/whistleblower/report/${accessKey}`,
|
||||
{
|
||||
method: 'GET',
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
}
|
||||
)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
|
||||
}
|
||||
|
||||
return response.json()
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// WORKFLOW ACTIONS
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Eingangsbestaetigung versenden (HinSchG ss 17 Abs. 1)
|
||||
*/
|
||||
export async function acknowledgeReport(id: string): Promise<WhistleblowerReport> {
|
||||
return fetchWithTimeout<WhistleblowerReport>(
|
||||
`${WB_API_BASE}/api/v1/admin/whistleblower/reports/${id}/acknowledge`,
|
||||
{
|
||||
method: 'POST'
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Untersuchung starten
|
||||
*/
|
||||
export async function startInvestigation(id: string): Promise<WhistleblowerReport> {
|
||||
return fetchWithTimeout<WhistleblowerReport>(
|
||||
`${WB_API_BASE}/api/v1/admin/whistleblower/reports/${id}/investigate`,
|
||||
{
|
||||
method: 'POST'
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Massnahme zu einer Meldung hinzufuegen
|
||||
*/
|
||||
export async function addMeasure(
|
||||
id: string,
|
||||
measure: Omit<WhistleblowerMeasure, 'id' | 'reportId' | 'completedAt'>
|
||||
): Promise<WhistleblowerMeasure> {
|
||||
return fetchWithTimeout<WhistleblowerMeasure>(
|
||||
`${WB_API_BASE}/api/v1/admin/whistleblower/reports/${id}/measures`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify(measure)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Meldung abschliessen mit Begruendung
|
||||
*/
|
||||
export async function closeReport(
|
||||
id: string,
|
||||
resolution: { reason: string; notes: string }
|
||||
): Promise<WhistleblowerReport> {
|
||||
return fetchWithTimeout<WhistleblowerReport>(
|
||||
`${WB_API_BASE}/api/v1/admin/whistleblower/reports/${id}/close`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify(resolution)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ANONYMOUS MESSAGING
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Nachricht im anonymen Kanal senden
|
||||
*/
|
||||
export async function sendMessage(
|
||||
reportId: string,
|
||||
message: string,
|
||||
role: 'reporter' | 'ombudsperson'
|
||||
): Promise<AnonymousMessage> {
|
||||
return fetchWithTimeout<AnonymousMessage>(
|
||||
`${WB_API_BASE}/api/v1/admin/whistleblower/reports/${reportId}/messages`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ senderRole: role, message })
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Nachrichten fuer eine Meldung abrufen
|
||||
*/
|
||||
export async function fetchMessages(reportId: string): Promise<AnonymousMessage[]> {
|
||||
return fetchWithTimeout<AnonymousMessage[]>(
|
||||
`${WB_API_BASE}/api/v1/admin/whistleblower/reports/${reportId}/messages`
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ATTACHMENTS
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Anhang zu einer Meldung hochladen
|
||||
*/
|
||||
export async function uploadAttachment(
|
||||
reportId: string,
|
||||
file: File
|
||||
): Promise<FileAttachment> {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
|
||||
const controller = new AbortController()
|
||||
const timeoutId = setTimeout(() => controller.abort(), 60000) // 60s for uploads
|
||||
|
||||
try {
|
||||
const headers: HeadersInit = {
|
||||
'X-Tenant-ID': getTenantId()
|
||||
}
|
||||
if (typeof window !== 'undefined') {
|
||||
const token = localStorage.getItem('authToken')
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`
|
||||
}
|
||||
}
|
||||
|
||||
const response = await fetch(
|
||||
`${WB_API_BASE}/api/v1/admin/whistleblower/reports/${reportId}/attachments`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: formData,
|
||||
signal: controller.signal
|
||||
}
|
||||
)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Upload fehlgeschlagen: ${response.statusText}`)
|
||||
}
|
||||
|
||||
return response.json()
|
||||
} finally {
|
||||
clearTimeout(timeoutId)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Anhang loeschen
|
||||
*/
|
||||
export async function deleteAttachment(id: string): Promise<void> {
|
||||
await fetchWithTimeout<void>(
|
||||
`${WB_API_BASE}/api/v1/admin/whistleblower/attachments/${id}`,
|
||||
{
|
||||
method: 'DELETE'
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// STATISTICS
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Statistiken fuer das Whistleblower-Dashboard abrufen
|
||||
*/
|
||||
export async function fetchWhistleblowerStatistics(): Promise<WhistleblowerStatistics> {
|
||||
return fetchWithTimeout<WhistleblowerStatistics>(
|
||||
`${WB_API_BASE}/api/v1/admin/whistleblower/statistics`
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// SDK PROXY FUNCTION (via Next.js proxy)
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Fetch Whistleblower-Daten via SDK Proxy mit Fallback auf Mock-Daten
|
||||
*/
|
||||
export async function fetchSDKWhistleblowerList(): Promise<{
|
||||
reports: WhistleblowerReport[]
|
||||
statistics: WhistleblowerStatistics
|
||||
}> {
|
||||
try {
|
||||
const [reportsResponse, statsResponse] = await Promise.all([
|
||||
fetchReports(),
|
||||
fetchWhistleblowerStatistics()
|
||||
])
|
||||
return {
|
||||
reports: reportsResponse.reports,
|
||||
statistics: statsResponse
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load Whistleblower data from API, using mock data:', error)
|
||||
// Fallback to mock data
|
||||
const reports = createMockReports()
|
||||
const statistics = createMockStatistics()
|
||||
return { reports, statistics }
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// MOCK DATA (Demo/Entwicklung)
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Erstellt Demo-Meldungen fuer Entwicklung und Praesentationen
|
||||
*/
|
||||
export function createMockReports(): WhistleblowerReport[] {
|
||||
const now = new Date()
|
||||
|
||||
// Helper: Berechne Fristen
|
||||
function calcDeadlines(receivedAt: Date): { ack: string; fb: string } {
|
||||
const ack = new Date(receivedAt)
|
||||
ack.setDate(ack.getDate() + 7)
|
||||
const fb = new Date(receivedAt)
|
||||
fb.setMonth(fb.getMonth() + 3)
|
||||
return { ack: ack.toISOString(), fb: fb.toISOString() }
|
||||
}
|
||||
|
||||
const received1 = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000)
|
||||
const deadlines1 = calcDeadlines(received1)
|
||||
|
||||
const received2 = new Date(now.getTime() - 14 * 24 * 60 * 60 * 1000)
|
||||
const deadlines2 = calcDeadlines(received2)
|
||||
|
||||
const received3 = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000)
|
||||
const deadlines3 = calcDeadlines(received3)
|
||||
|
||||
const received4 = new Date(now.getTime() - 90 * 24 * 60 * 60 * 1000)
|
||||
const deadlines4 = calcDeadlines(received4)
|
||||
|
||||
return [
|
||||
// Report 1: Neu
|
||||
{
|
||||
id: 'wb-001',
|
||||
referenceNumber: 'WB-2026-000001',
|
||||
accessKey: generateAccessKey(),
|
||||
category: 'corruption',
|
||||
status: 'new',
|
||||
priority: 'high',
|
||||
title: 'Unregelmaessigkeiten bei Auftragsvergabe',
|
||||
description: 'Bei der Vergabe des IT-Rahmenvertrags im November wurden offenbar Angebote eines bestimmten Anbieters bevorzugt. Der zustaendige Abteilungsleiter hat private Verbindungen zum Geschaeftsfuehrer des Anbieters.',
|
||||
isAnonymous: true,
|
||||
receivedAt: received1.toISOString(),
|
||||
deadlineAcknowledgment: deadlines1.ack,
|
||||
deadlineFeedback: deadlines1.fb,
|
||||
measures: [],
|
||||
messages: [],
|
||||
attachments: [],
|
||||
auditTrail: [
|
||||
{
|
||||
id: 'audit-001',
|
||||
action: 'report_created',
|
||||
description: 'Meldung ueber Online-Meldeformular eingegangen',
|
||||
performedBy: 'system',
|
||||
performedAt: received1.toISOString()
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// Report 2: In Pruefung (under_review)
|
||||
{
|
||||
id: 'wb-002',
|
||||
referenceNumber: 'WB-2026-000002',
|
||||
accessKey: generateAccessKey(),
|
||||
category: 'data_protection',
|
||||
status: 'under_review',
|
||||
priority: 'normal',
|
||||
title: 'Unerlaubte Weitergabe von Kundendaten',
|
||||
description: 'Ein Mitarbeiter der Vertriebsabteilung gibt regelmaessig Kundenlisten an externe Dienstleister weiter, ohne dass eine Auftragsverarbeitungsvereinbarung vorliegt.',
|
||||
isAnonymous: false,
|
||||
reporterName: 'Maria Schmidt',
|
||||
reporterEmail: 'maria.schmidt@example.de',
|
||||
assignedTo: 'DSB Mueller',
|
||||
receivedAt: received2.toISOString(),
|
||||
acknowledgedAt: new Date(received2.getTime() + 3 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
deadlineAcknowledgment: deadlines2.ack,
|
||||
deadlineFeedback: deadlines2.fb,
|
||||
measures: [],
|
||||
messages: [
|
||||
{
|
||||
id: 'msg-001',
|
||||
reportId: 'wb-002',
|
||||
senderRole: 'ombudsperson',
|
||||
message: 'Vielen Dank fuer Ihre Meldung. Koennen Sie uns mitteilen, welche Dienstleister konkret betroffen sind?',
|
||||
createdAt: new Date(received2.getTime() + 5 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
isRead: true
|
||||
},
|
||||
{
|
||||
id: 'msg-002',
|
||||
reportId: 'wb-002',
|
||||
senderRole: 'reporter',
|
||||
message: 'Es handelt sich um die Firma DataServ GmbH und MarketPro AG. Die Listen werden per unverschluesselter E-Mail versendet.',
|
||||
createdAt: new Date(received2.getTime() + 7 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
isRead: true
|
||||
}
|
||||
],
|
||||
attachments: [
|
||||
{
|
||||
id: 'att-001',
|
||||
fileName: 'email_screenshot_vertrieb.png',
|
||||
fileSize: 245000,
|
||||
mimeType: 'image/png',
|
||||
uploadedAt: received2.toISOString(),
|
||||
uploadedBy: 'reporter'
|
||||
}
|
||||
],
|
||||
auditTrail: [
|
||||
{
|
||||
id: 'audit-002',
|
||||
action: 'report_created',
|
||||
description: 'Meldung per E-Mail eingegangen',
|
||||
performedBy: 'system',
|
||||
performedAt: received2.toISOString()
|
||||
},
|
||||
{
|
||||
id: 'audit-003',
|
||||
action: 'acknowledged',
|
||||
description: 'Eingangsbestaetigung an Hinweisgeber versendet',
|
||||
performedBy: 'DSB Mueller',
|
||||
performedAt: new Date(received2.getTime() + 3 * 24 * 60 * 60 * 1000).toISOString()
|
||||
},
|
||||
{
|
||||
id: 'audit-004',
|
||||
action: 'status_changed',
|
||||
description: 'Status geaendert: Bestaetigt -> In Pruefung',
|
||||
performedBy: 'DSB Mueller',
|
||||
performedAt: new Date(received2.getTime() + 5 * 24 * 60 * 60 * 1000).toISOString()
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// Report 3: Untersuchung (investigation)
|
||||
{
|
||||
id: 'wb-003',
|
||||
referenceNumber: 'WB-2026-000003',
|
||||
accessKey: generateAccessKey(),
|
||||
category: 'product_safety',
|
||||
status: 'investigation',
|
||||
priority: 'critical',
|
||||
title: 'Fehlende Sicherheitspruefungen bei Produktfreigabe',
|
||||
description: 'In der Fertigung werden seit Wochen Produkte ohne die vorgeschriebenen Sicherheitspruefungen freigegeben. Pruefprotokolle werden nachtraeglich erstellt, ohne dass tatsaechliche Pruefungen stattfinden.',
|
||||
isAnonymous: true,
|
||||
assignedTo: 'Qualitaetsbeauftragter Weber',
|
||||
receivedAt: received3.toISOString(),
|
||||
acknowledgedAt: new Date(received3.getTime() + 2 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
deadlineAcknowledgment: deadlines3.ack,
|
||||
deadlineFeedback: deadlines3.fb,
|
||||
measures: [
|
||||
{
|
||||
id: 'msr-001',
|
||||
reportId: 'wb-003',
|
||||
title: 'Sofortiger Produktionsstopp fuer betroffene Charge',
|
||||
description: 'Produktion der betroffenen Produktlinie stoppen bis Pruefverfahren sichergestellt ist',
|
||||
status: 'completed',
|
||||
responsible: 'Fertigungsleitung',
|
||||
dueDate: new Date(received3.getTime() + 5 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
completedAt: new Date(received3.getTime() + 3 * 24 * 60 * 60 * 1000).toISOString()
|
||||
},
|
||||
{
|
||||
id: 'msr-002',
|
||||
reportId: 'wb-003',
|
||||
title: 'Externe Pruefung der Pruefprotokolle',
|
||||
description: 'Unabhaengige Pruefstelle mit der Revision aller Pruefprotokolle der letzten 6 Monate beauftragen',
|
||||
status: 'in_progress',
|
||||
responsible: 'Qualitaetsmanagement',
|
||||
dueDate: new Date(now.getTime() + 14 * 24 * 60 * 60 * 1000).toISOString()
|
||||
}
|
||||
],
|
||||
messages: [],
|
||||
attachments: [
|
||||
{
|
||||
id: 'att-002',
|
||||
fileName: 'pruefprotokoll_vergleich.pdf',
|
||||
fileSize: 890000,
|
||||
mimeType: 'application/pdf',
|
||||
uploadedAt: new Date(received3.getTime() + 5 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
uploadedBy: 'ombudsperson'
|
||||
}
|
||||
],
|
||||
auditTrail: [
|
||||
{
|
||||
id: 'audit-005',
|
||||
action: 'report_created',
|
||||
description: 'Meldung ueber Online-Meldeformular eingegangen',
|
||||
performedBy: 'system',
|
||||
performedAt: received3.toISOString()
|
||||
},
|
||||
{
|
||||
id: 'audit-006',
|
||||
action: 'acknowledged',
|
||||
description: 'Eingangsbestaetigung versendet',
|
||||
performedBy: 'Qualitaetsbeauftragter Weber',
|
||||
performedAt: new Date(received3.getTime() + 2 * 24 * 60 * 60 * 1000).toISOString()
|
||||
},
|
||||
{
|
||||
id: 'audit-007',
|
||||
action: 'investigation_started',
|
||||
description: 'Formelle Untersuchung eingeleitet',
|
||||
performedBy: 'Qualitaetsbeauftragter Weber',
|
||||
performedAt: new Date(received3.getTime() + 5 * 24 * 60 * 60 * 1000).toISOString()
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// Report 4: Abgeschlossen (closed)
|
||||
{
|
||||
id: 'wb-004',
|
||||
referenceNumber: 'WB-2026-000004',
|
||||
accessKey: generateAccessKey(),
|
||||
category: 'fraud',
|
||||
status: 'closed',
|
||||
priority: 'high',
|
||||
title: 'Gefaelschte Reisekostenabrechnungen',
|
||||
description: 'Ein leitender Mitarbeiter reicht seit ueber einem Jahr gefaelschte Reisekostenabrechnungen ein. Hotelrechnungen werden manipuliert, Taxiquittungen erfunden.',
|
||||
isAnonymous: false,
|
||||
reporterName: 'Thomas Klein',
|
||||
reporterEmail: 'thomas.klein@example.de',
|
||||
reporterPhone: '+49 170 9876543',
|
||||
assignedTo: 'Compliance-Abteilung',
|
||||
receivedAt: received4.toISOString(),
|
||||
acknowledgedAt: new Date(received4.getTime() + 3 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
deadlineAcknowledgment: deadlines4.ack,
|
||||
deadlineFeedback: deadlines4.fb,
|
||||
closedAt: new Date(now.getTime() - 10 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
measures: [
|
||||
{
|
||||
id: 'msr-003',
|
||||
reportId: 'wb-004',
|
||||
title: 'Interne Revision der Reisekosten',
|
||||
description: 'Pruefung aller Reisekostenabrechnungen des betroffenen Mitarbeiters der letzten 24 Monate',
|
||||
status: 'completed',
|
||||
responsible: 'Interne Revision',
|
||||
dueDate: new Date(received4.getTime() + 30 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
completedAt: new Date(received4.getTime() + 25 * 24 * 60 * 60 * 1000).toISOString()
|
||||
},
|
||||
{
|
||||
id: 'msr-004',
|
||||
reportId: 'wb-004',
|
||||
title: 'Arbeitsrechtliche Konsequenzen',
|
||||
description: 'Einleitung arbeitsrechtlicher Schritte nach Bestaetigung des Betrugs',
|
||||
status: 'completed',
|
||||
responsible: 'Personalabteilung',
|
||||
dueDate: new Date(received4.getTime() + 60 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
completedAt: new Date(received4.getTime() + 55 * 24 * 60 * 60 * 1000).toISOString()
|
||||
}
|
||||
],
|
||||
messages: [],
|
||||
attachments: [
|
||||
{
|
||||
id: 'att-003',
|
||||
fileName: 'vergleich_originalrechnung_einreichung.pdf',
|
||||
fileSize: 567000,
|
||||
mimeType: 'application/pdf',
|
||||
uploadedAt: received4.toISOString(),
|
||||
uploadedBy: 'reporter'
|
||||
}
|
||||
],
|
||||
auditTrail: [
|
||||
{
|
||||
id: 'audit-008',
|
||||
action: 'report_created',
|
||||
description: 'Meldung per Brief eingegangen',
|
||||
performedBy: 'system',
|
||||
performedAt: received4.toISOString()
|
||||
},
|
||||
{
|
||||
id: 'audit-009',
|
||||
action: 'acknowledged',
|
||||
description: 'Eingangsbestaetigung versendet',
|
||||
performedBy: 'Compliance-Abteilung',
|
||||
performedAt: new Date(received4.getTime() + 3 * 24 * 60 * 60 * 1000).toISOString()
|
||||
},
|
||||
{
|
||||
id: 'audit-010',
|
||||
action: 'closed',
|
||||
description: 'Fall abgeschlossen - Betrug bestaetigt, arbeitsrechtliche Massnahmen eingeleitet',
|
||||
performedBy: 'Compliance-Abteilung',
|
||||
performedAt: new Date(now.getTime() - 10 * 24 * 60 * 60 * 1000).toISOString()
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Berechnet Statistiken aus den Mock-Daten
|
||||
*/
|
||||
export function createMockStatistics(): WhistleblowerStatistics {
|
||||
const reports = createMockReports()
|
||||
const now = new Date()
|
||||
|
||||
const byStatus: Record<ReportStatus, number> = {
|
||||
new: 0,
|
||||
acknowledged: 0,
|
||||
under_review: 0,
|
||||
investigation: 0,
|
||||
measures_taken: 0,
|
||||
closed: 0,
|
||||
rejected: 0
|
||||
}
|
||||
|
||||
const byCategory: Record<ReportCategory, number> = {
|
||||
corruption: 0,
|
||||
fraud: 0,
|
||||
data_protection: 0,
|
||||
discrimination: 0,
|
||||
environment: 0,
|
||||
competition: 0,
|
||||
product_safety: 0,
|
||||
tax_evasion: 0,
|
||||
other: 0
|
||||
}
|
||||
|
||||
reports.forEach(r => {
|
||||
byStatus[r.status]++
|
||||
byCategory[r.category]++
|
||||
})
|
||||
|
||||
const closedStatuses: ReportStatus[] = ['closed', 'rejected']
|
||||
|
||||
// Pruefe ueberfaellige Eingangsbestaetigungen
|
||||
const overdueAcknowledgment = reports.filter(r => {
|
||||
if (r.status !== 'new') return false
|
||||
return now > new Date(r.deadlineAcknowledgment)
|
||||
}).length
|
||||
|
||||
// Pruefe ueberfaellige Rueckmeldungen
|
||||
const overdueFeedback = reports.filter(r => {
|
||||
if (closedStatuses.includes(r.status)) return false
|
||||
return now > new Date(r.deadlineFeedback)
|
||||
}).length
|
||||
|
||||
return {
|
||||
totalReports: reports.length,
|
||||
newReports: byStatus.new,
|
||||
underReview: byStatus.under_review + byStatus.investigation,
|
||||
closed: byStatus.closed + byStatus.rejected,
|
||||
overdueAcknowledgment,
|
||||
overdueFeedback,
|
||||
byCategory,
|
||||
byStatus
|
||||
}
|
||||
}
|
||||
export {
|
||||
fetchReports,
|
||||
fetchReport,
|
||||
updateReport,
|
||||
deleteReport,
|
||||
submitPublicReport,
|
||||
fetchReportByAccessKey,
|
||||
acknowledgeReport,
|
||||
startInvestigation,
|
||||
addMeasure,
|
||||
closeReport,
|
||||
sendMessage,
|
||||
fetchMessages,
|
||||
uploadAttachment,
|
||||
deleteAttachment,
|
||||
fetchWhistleblowerStatistics,
|
||||
} from './api-operations'
|
||||
|
||||
export {
|
||||
fetchSDKWhistleblowerList,
|
||||
createMockReports,
|
||||
createMockStatistics,
|
||||
} from './api-mock-data'
|
||||
|
||||
Reference in New Issue
Block a user