57f418f7c9
Backend: - ComplianceDashboardService: traffic-light status per ComplianceArea (KCANG/FINANCE/DSGVO/VEREIN) based on deadlines, payments, board positions - RetentionService: scheduled anonymization of expired member data (KCanG §24, 5 years), with dry-run preview and retention report endpoints - ComplianceDeadlineSeeder: seeds 5 standard recurring deadlines on club creation - ComplianceDashboardController: GET /api/v1/compliance/dashboard, GET /retention, POST /retention/preview - Repository additions: countOverdue, countActive board positions/members Frontend: - /compliance page with traffic-light status cards per area - Overdue deadlines section (highlighted red) with 'days overdue' badges - Upcoming deadlines with 'days until due' badges and 'Complete' buttons - Retention info cards (KCanG §24: 5y, AO §147: 10y, DSGVO: 2y) - Navigation: added 'Compliance-Status' to sidebar under Compliance group - compliance-dashboard.ts service with mock data for dev mode Build verified: pnpm build passes clean.
182 lines
4.4 KiB
TypeScript
182 lines
4.4 KiB
TypeScript
import { apiClient } from "@/lib/api-client"
|
|
|
|
export interface ComplianceStatus {
|
|
KCANG: "GREEN" | "YELLOW" | "RED"
|
|
FINANCE: "GREEN" | "YELLOW" | "RED"
|
|
DSGVO: "GREEN" | "YELLOW" | "RED"
|
|
VEREIN: "GREEN" | "YELLOW" | "RED"
|
|
}
|
|
|
|
export interface ComplianceDeadline {
|
|
id: string
|
|
clubId: string
|
|
area: "KCANG" | "FINANCE" | "DSGVO" | "VEREIN"
|
|
title: string
|
|
description: string | null
|
|
dueDate: string
|
|
isRecurring: boolean
|
|
recurrenceRule: string | null
|
|
completedAt: string | null
|
|
completedBy: string | null
|
|
}
|
|
|
|
export interface ComplianceDashboardResponse {
|
|
status: ComplianceStatus
|
|
upcomingDeadlines: ComplianceDeadline[]
|
|
overdueDeadlines: ComplianceDeadline[]
|
|
}
|
|
|
|
export interface RetentionReport {
|
|
totalAnonymized: number
|
|
upcomingAnonymizations: number
|
|
currentCutoffDate: string
|
|
retentionSchedule: RetentionScheduleItem[]
|
|
}
|
|
|
|
export interface RetentionScheduleItem {
|
|
legalBasis: string
|
|
description: string
|
|
retentionYears: number
|
|
}
|
|
|
|
export interface RetentionPreview {
|
|
affectedCount: number
|
|
items: RetentionPreviewItem[]
|
|
}
|
|
|
|
export interface RetentionPreviewItem {
|
|
memberId: string
|
|
membershipNumber: string
|
|
membershipDate: string
|
|
reason: string
|
|
}
|
|
|
|
// --- Mock data for development ---
|
|
|
|
const mockDashboard: ComplianceDashboardResponse = {
|
|
status: {
|
|
KCANG: "GREEN",
|
|
FINANCE: "YELLOW",
|
|
DSGVO: "GREEN",
|
|
VEREIN: "GREEN",
|
|
},
|
|
upcomingDeadlines: [
|
|
{
|
|
id: "d1",
|
|
clubId: "c1",
|
|
area: "FINANCE",
|
|
title: "Kassenprüfung durchführen",
|
|
description: "Prüfung der Vereinskasse durch gewählte Kassenprüfer",
|
|
dueDate: new Date(Date.now() + 14 * 86400000).toISOString().split("T")[0],
|
|
isRecurring: true,
|
|
recurrenceRule: "YEARLY",
|
|
completedAt: null,
|
|
completedBy: null,
|
|
},
|
|
{
|
|
id: "d2",
|
|
clubId: "c1",
|
|
area: "DSGVO",
|
|
title: "VVT aktualisieren",
|
|
description:
|
|
"Jährliche Überprüfung des Verzeichnisses von Verarbeitungstätigkeiten",
|
|
dueDate: new Date(Date.now() + 28 * 86400000).toISOString().split("T")[0],
|
|
isRecurring: true,
|
|
recurrenceRule: "YEARLY",
|
|
completedAt: null,
|
|
completedBy: null,
|
|
},
|
|
],
|
|
overdueDeadlines: [
|
|
{
|
|
id: "d3",
|
|
clubId: "c1",
|
|
area: "FINANCE",
|
|
title: "EÜR erstellen",
|
|
description: "Einnahmen-Überschuss-Rechnung für das Vorjahr",
|
|
dueDate: new Date(Date.now() - 10 * 86400000).toISOString().split("T")[0],
|
|
isRecurring: true,
|
|
recurrenceRule: "YEARLY",
|
|
completedAt: null,
|
|
completedBy: null,
|
|
},
|
|
],
|
|
}
|
|
|
|
const mockRetentionReport: RetentionReport = {
|
|
totalAnonymized: 3,
|
|
upcomingAnonymizations: 1,
|
|
currentCutoffDate: new Date(Date.now() - 5 * 365 * 86400000)
|
|
.toISOString()
|
|
.split("T")[0],
|
|
retentionSchedule: [
|
|
{
|
|
legalBasis: "KCanG §24",
|
|
description: "Mitgliederdaten nach Austritt",
|
|
retentionYears: 5,
|
|
},
|
|
{
|
|
legalBasis: "AO §147",
|
|
description: "Finanzdaten (Aufbewahrungspflicht)",
|
|
retentionYears: 10,
|
|
},
|
|
{
|
|
legalBasis: "DSGVO",
|
|
description: "Kommunikationsdaten (inaktiv)",
|
|
retentionYears: 2,
|
|
},
|
|
],
|
|
}
|
|
|
|
// --- API functions ---
|
|
|
|
export async function getComplianceDashboard(
|
|
upcomingDays = 30
|
|
): Promise<ComplianceDashboardResponse> {
|
|
if (process.env.NEXT_PUBLIC_USE_MOCK === "true") {
|
|
return mockDashboard
|
|
}
|
|
return apiClient<ComplianceDashboardResponse>(
|
|
`/api/v1/compliance/dashboard?upcomingDays=${upcomingDays}`
|
|
)
|
|
}
|
|
|
|
export async function getRetentionReport(): Promise<RetentionReport> {
|
|
if (process.env.NEXT_PUBLIC_USE_MOCK === "true") {
|
|
return mockRetentionReport
|
|
}
|
|
return apiClient<RetentionReport>("/api/v1/compliance/dashboard/retention")
|
|
}
|
|
|
|
export async function previewRetention(): Promise<RetentionPreview> {
|
|
if (process.env.NEXT_PUBLIC_USE_MOCK === "true") {
|
|
return { affectedCount: 0, items: [] }
|
|
}
|
|
return apiClient<RetentionPreview>(
|
|
"/api/v1/compliance/dashboard/retention/preview",
|
|
{
|
|
method: "POST",
|
|
}
|
|
)
|
|
}
|
|
|
|
export async function completeDeadline(
|
|
deadlineId: string,
|
|
completedBy: string
|
|
): Promise<ComplianceDeadline> {
|
|
if (process.env.NEXT_PUBLIC_USE_MOCK === "true") {
|
|
return {
|
|
...mockDashboard.upcomingDeadlines[0],
|
|
completedAt: new Date().toISOString(),
|
|
completedBy,
|
|
}
|
|
}
|
|
return apiClient<ComplianceDeadline>(
|
|
`/api/v1/compliance/deadlines/${deadlineId}/complete`,
|
|
{
|
|
method: "POST",
|
|
body: JSON.stringify({ completedBy }),
|
|
}
|
|
)
|
|
}
|