import { Response } from 'express'; import { AuthRequest } from '../types/index.js'; import * as gdprService from '../services/gdpr.service.js'; import * as consentService from '../services/consent.service.js'; import * as consentPublicService from '../services/consent-public.service.js'; import * as appSettingService from '../services/appSetting.service.js'; import { canAccessCustomer } from '../utils/accessControl.js'; import { createAuditLog, logChange } from '../services/audit.service.js'; import { ConsentType, DeletionRequestStatus } from '@prisma/client'; import prisma from '../lib/prisma.js'; import path from 'path'; import fs from 'fs'; import { sendEmail, SmtpCredentials } from '../services/smtpService.js'; import { getSystemEmailCredentials } from '../services/emailProvider/emailProviderService.js'; import * as authorizationService from '../services/authorization.service.js'; /** * Kundendaten exportieren (DSGVO Art. 15) */ export async function exportCustomerData(req: AuthRequest, res: Response) { try { const customerId = parseInt(req.params.customerId); const format = (req.query.format as string) || 'json'; const data = await gdprService.exportCustomerData(customerId); // Audit-Log für Export await createAuditLog({ userId: req.user?.userId, userEmail: req.user?.email || 'unknown', action: 'EXPORT', resourceType: 'GDPR', resourceId: customerId.toString(), resourceLabel: `Datenexport für ${data.dataSubject.name}`, endpoint: req.path, httpMethod: req.method, ipAddress: req.socket.remoteAddress || 'unknown', dataSubjectId: customerId, legalBasis: 'DSGVO Art. 15', }); if (format === 'json') { res.setHeader('Content-Type', 'application/json'); res.setHeader( 'Content-Disposition', `attachment; filename="datenexport_${data.dataSubject.customerNumber}_${new Date().toISOString().split('T')[0]}.json"` ); res.json(data); } else { // Für PDF würde hier PDFKit verwendet werden res.json({ success: true, data }); } } catch (error) { console.error('Fehler beim Datenexport:', error); res.status(500).json({ success: false, error: error instanceof Error ? error.message : 'Fehler beim Datenexport', }); } } /** * Löschanfrage erstellen */ export async function createDeletionRequest(req: AuthRequest, res: Response) { try { const customerId = parseInt(req.params.id); const { requestSource } = req.body; const request = await gdprService.createDeletionRequest({ customerId, requestSource: requestSource || 'portal', requestedBy: req.user?.email || 'unknown', }); await logChange({ req, action: 'CREATE', resourceType: 'DeletionRequest', resourceId: request.id.toString(), label: `Löschanfrage erstellt für Kunde #${customerId}`, customerId, }); res.status(201).json({ success: true, data: request }); } catch (error) { console.error('Fehler beim Erstellen der Löschanfrage:', error); res.status(400).json({ success: false, error: error instanceof Error ? error.message : 'Fehler beim Erstellen', }); } } /** * Alle Löschanfragen abrufen */ export async function getDeletionRequests(req: AuthRequest, res: Response) { try { const { status, page, limit } = req.query; const result = await gdprService.getDeletionRequests({ status: status as DeletionRequestStatus | undefined, page: page ? parseInt(page as string) : 1, limit: limit ? parseInt(limit as string) : 20, }); res.json({ success: true, ...result }); } catch (error) { console.error('Fehler beim Abrufen der Löschanfragen:', error); res.status(500).json({ success: false, error: 'Fehler beim Abrufen' }); } } /** * Einzelne Löschanfrage abrufen */ export async function getDeletionRequest(req: AuthRequest, res: Response) { try { const id = parseInt(req.params.id); const request = await gdprService.getDeletionRequest(id); if (!request) { return res.status(404).json({ success: false, error: 'Löschanfrage nicht gefunden' }); } res.json({ success: true, data: request }); } catch (error) { console.error('Fehler beim Abrufen der Löschanfrage:', error); res.status(500).json({ success: false, error: 'Fehler beim Abrufen' }); } } /** * Löschanfrage bearbeiten */ export async function processDeletionRequest(req: AuthRequest, res: Response) { try { const id = parseInt(req.params.id); const { action, retentionReason } = req.body; if (!['complete', 'partial', 'reject'].includes(action)) { return res.status(400).json({ success: false, error: 'Ungültige Aktion. Erlaubt: complete, partial, reject', }); } const result = await gdprService.processDeletionRequest(id, { processedBy: req.user?.email || 'unknown', action, retentionReason, }); // Audit-Log für Löschung await createAuditLog({ userId: req.user?.userId, userEmail: req.user?.email || 'unknown', action: 'ANONYMIZE', resourceType: 'GDPR', resourceId: id.toString(), resourceLabel: `Löschanfrage ${action}`, endpoint: req.path, httpMethod: req.method, ipAddress: req.socket.remoteAddress || 'unknown', dataSubjectId: result.customerId, legalBasis: 'DSGVO Art. 17', }); res.json({ success: true, data: result }); } catch (error) { console.error('Fehler beim Bearbeiten der Löschanfrage:', error); res.status(400).json({ success: false, error: error instanceof Error ? error.message : 'Fehler beim Bearbeiten', }); } } /** * Löschnachweis-PDF herunterladen */ export async function getDeletionProof(req: AuthRequest, res: Response) { try { const id = parseInt(req.params.id); const request = await gdprService.getDeletionRequest(id); if (!request) { return res.status(404).json({ success: false, error: 'Löschanfrage nicht gefunden' }); } if (!request.proofDocument) { return res.status(404).json({ success: false, error: 'Kein Löschnachweis vorhanden' }); } // Path-Traversal-Schutz: proofDocument aus der DB darf nur unter uploads/ liegen const uploadsDir = path.resolve(process.cwd(), 'uploads'); const filepath = path.resolve(uploadsDir, request.proofDocument); if (!filepath.startsWith(uploadsDir + path.sep)) { return res.status(400).json({ success: false, error: 'Ungültiger Dateipfad' }); } if (!fs.existsSync(filepath)) { return res.status(404).json({ success: false, error: 'Datei nicht gefunden' }); } res.download(filepath); } catch (error) { console.error('Fehler beim Download des Löschnachweises:', error); res.status(500).json({ success: false, error: 'Fehler beim Download' }); } } /** * DSGVO-Dashboard Statistiken */ export async function getDashboardStats(req: AuthRequest, res: Response) { try { const stats = await gdprService.getGDPRDashboardStats(); res.json({ success: true, data: stats }); } catch (error) { console.error('Fehler beim Abrufen der Dashboard-Statistiken:', error); res.status(500).json({ success: false, error: 'Fehler beim Abrufen' }); } } // ==================== CONSENT ENDPOINTS ==================== /** * Einwilligungen eines Kunden abrufen */ export async function getCustomerConsents(req: AuthRequest, res: Response) { try { const customerId = parseInt(req.params.customerId); if (!(await canAccessCustomer(req, res, customerId))) return; const consents = await consentService.getCustomerConsents(customerId); // Labels hinzufügen const consentsWithLabels = consents.map((c) => ({ ...c, label: consentService.CONSENT_TYPE_LABELS[c.consentType as ConsentType]?.label, description: consentService.CONSENT_TYPE_LABELS[c.consentType as ConsentType]?.description, })); res.json({ success: true, data: consentsWithLabels }); } catch (error) { console.error('Fehler beim Abrufen der Einwilligungen:', error); res.status(500).json({ success: false, error: 'Fehler beim Abrufen' }); } } /** * Consent-Status prüfen (hat der Kunde vollständig zugestimmt?) */ export async function checkConsentStatus(req: AuthRequest, res: Response) { try { const customerId = parseInt(req.params.customerId); if (!(await canAccessCustomer(req, res, customerId))) return; const result = await consentService.hasFullConsent(customerId); res.json({ success: true, data: result }); } catch (error) { console.error('Fehler beim Consent-Check:', error); res.status(500).json({ success: false, error: 'Fehler beim Consent-Check' }); } } /** * Einwilligung aktualisieren (nur Kundenportal-Benutzer!) */ export async function updateCustomerConsent(req: AuthRequest, res: Response) { try { const customerId = parseInt(req.params.customerId); const consentType = req.params.consentType as ConsentType; const { status, source, documentPath, version } = req.body; // Nur Kundenportal-Benutzer dürfen Einwilligungen ändern if (!(req.user as any)?.isCustomerPortal) { return res.status(403).json({ success: false, error: 'Nur Kunden können Einwilligungen ändern', }); } // Portal: nur eigene + vertretene Kunden const allowed = [ (req.user as any).customerId, ...((req.user as any).representedCustomerIds || []), ]; if (!allowed.includes(customerId)) { return res.status(403).json({ success: false, error: 'Keine Berechtigung für diesen Kunden', }); } if (!Object.values(ConsentType).includes(consentType)) { return res.status(400).json({ success: false, error: 'Ungültiger Consent-Typ' }); } const consentLabels: Record = { DATA_PROCESSING: 'Datenverarbeitung', MARKETING_EMAIL: 'E-Mail-Marketing', MARKETING_PHONE: 'Telefonmarketing', DATA_SHARING_PARTNER: 'Datenweitergabe', }; const consent = await consentService.updateConsent(customerId, consentType, { status, source: source || 'portal', documentPath, version, ipAddress: req.socket.remoteAddress, createdBy: req.user?.email || 'unknown', }); const consentName = consentLabels[consentType] || consentType; await logChange({ req, action: 'UPDATE', resourceType: 'CustomerConsent', label: status === 'GRANTED' ? `Einwilligung "${consentName}" erteilt` : `Einwilligung "${consentName}" widerrufen`, details: { einwilligung: consentName, status, quelle: source || 'portal' }, customerId, }); res.json({ success: true, data: consent }); } catch (error) { console.error('Fehler beim Aktualisieren der Einwilligung:', error); res.status(400).json({ success: false, error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren', }); } } /** * Consent-Übersicht für Dashboard */ export async function getConsentOverview(req: AuthRequest, res: Response) { try { const overview = await consentService.getConsentOverview(); // Labels hinzufügen const overviewWithLabels = Object.entries(overview).map(([type, stats]) => ({ type, label: consentService.CONSENT_TYPE_LABELS[type as ConsentType]?.label, description: consentService.CONSENT_TYPE_LABELS[type as ConsentType]?.description, ...stats, })); res.json({ success: true, data: overviewWithLabels }); } catch (error) { console.error('Fehler beim Abrufen der Consent-Übersicht:', error); res.status(500).json({ success: false, error: 'Fehler beim Abrufen' }); } } // ==================== PRIVACY POLICY ENDPOINTS ==================== /** * Datenschutzerklärung abrufen (HTML) */ export async function getPrivacyPolicy(req: AuthRequest, res: Response) { try { const html = await appSettingService.getSetting('privacyPolicyHtml'); res.json({ success: true, data: { html: html || '' } }); } catch (error) { console.error('Fehler beim Abrufen der Datenschutzerklärung:', error); res.status(500).json({ success: false, error: 'Fehler beim Abrufen' }); } } /** * Datenschutzerklärung speichern (HTML) */ export async function updatePrivacyPolicy(req: AuthRequest, res: Response) { try { const { html } = req.body; if (typeof html !== 'string') { return res.status(400).json({ success: false, error: 'HTML-Inhalt erforderlich' }); } await appSettingService.setSetting('privacyPolicyHtml', html); await logChange({ req, action: 'UPDATE', resourceType: 'PrivacyPolicy', label: `Datenschutzerklärung aktualisiert`, }); res.json({ success: true, message: 'Datenschutzerklärung gespeichert' }); } catch (error) { console.error('Fehler beim Speichern der Datenschutzerklärung:', error); res.status(500).json({ success: false, error: 'Fehler beim Speichern' }); } } /** * Vollmacht-Vorlage abrufen (HTML) */ export async function getAuthorizationTemplate(req: AuthRequest, res: Response) { try { const html = await appSettingService.getSetting('authorizationTemplateHtml'); res.json({ success: true, data: { html: html || '' } }); } catch (error) { console.error('Fehler beim Abrufen der Vollmacht-Vorlage:', error); res.status(500).json({ success: false, error: 'Fehler beim Abrufen' }); } } /** * Vollmacht-Vorlage speichern (HTML) */ export async function updateAuthorizationTemplate(req: AuthRequest, res: Response) { try { const { html } = req.body; if (typeof html !== 'string') { return res.status(400).json({ success: false, error: 'HTML-Inhalt erforderlich' }); } await appSettingService.setSetting('authorizationTemplateHtml', html); await logChange({ req, action: 'UPDATE', resourceType: 'AuthorizationTemplate', label: `Vollmacht-Vorlage aktualisiert`, }); res.json({ success: true, message: 'Vollmacht-Vorlage gespeichert' }); } catch (error) { console.error('Fehler beim Speichern der Vollmacht-Vorlage:', error); res.status(500).json({ success: false, error: 'Fehler beim Speichern' }); } } // ==================== IMPRESSUM & WEBSITE-DATENSCHUTZ ==================== export async function getImprint(req: AuthRequest, res: Response) { try { const html = await appSettingService.getSetting('imprintHtml'); res.json({ success: true, data: { html: html || '' } }); } catch (error) { res.status(500).json({ success: false, error: 'Fehler beim Abrufen' }); } } export async function updateImprint(req: AuthRequest, res: Response) { try { const { html } = req.body; if (typeof html !== 'string') return res.status(400).json({ success: false, error: 'HTML-Inhalt erforderlich' }); await appSettingService.setSetting('imprintHtml', html); await logChange({ req, action: 'UPDATE', resourceType: 'AppSetting', label: 'Impressum aktualisiert' }); res.json({ success: true, message: 'Impressum gespeichert' }); } catch (error) { res.status(500).json({ success: false, error: 'Fehler beim Speichern' }); } } export async function getWebsitePrivacyPolicy(req: AuthRequest, res: Response) { try { const html = await appSettingService.getSetting('websitePrivacyPolicyHtml'); res.json({ success: true, data: { html: html || '' } }); } catch (error) { res.status(500).json({ success: false, error: 'Fehler beim Abrufen' }); } } export async function updateWebsitePrivacyPolicy(req: AuthRequest, res: Response) { try { const { html } = req.body; if (typeof html !== 'string') return res.status(400).json({ success: false, error: 'HTML-Inhalt erforderlich' }); await appSettingService.setSetting('websitePrivacyPolicyHtml', html); await logChange({ req, action: 'UPDATE', resourceType: 'AppSetting', label: 'Website-Datenschutzerklärung aktualisiert' }); res.json({ success: true, message: 'Website-Datenschutzerklärung gespeichert' }); } catch (error) { res.status(500).json({ success: false, error: 'Fehler beim Speichern' }); } } // ==================== SEND CONSENT LINK ==================== /** * Consent-Link an Kunden senden */ // ==================== PORTAL ENDPOINTS ==================== /** * Portal: Eigene Datenschutzseite (Privacy Policy + Consent-Status) */ export async function getMyPrivacy(req: AuthRequest, res: Response) { try { const user = req.user as any; if (!user?.isCustomerPortal || !user?.customerId) { return res.status(403).json({ success: false, error: 'Nur für Kundenportal-Benutzer' }); } const customerId = user.customerId; const [privacyPolicyHtml, consents] = await Promise.all([ consentPublicService.getPrivacyPolicyHtml(customerId), consentService.getCustomerConsents(customerId), ]); const consentsWithLabels = consents.map((c) => ({ ...c, label: consentService.CONSENT_TYPE_LABELS[c.consentType as ConsentType]?.label, description: consentService.CONSENT_TYPE_LABELS[c.consentType as ConsentType]?.description, })); res.json({ success: true, data: { privacyPolicyHtml, consents: consentsWithLabels, }, }); } catch (error) { console.error('Fehler beim Laden der Portal-Datenschutzseite:', error); res.status(500).json({ success: false, error: 'Fehler beim Laden' }); } } /** * Portal: Datenschutzerklärung als PDF */ export async function getMyPrivacyPdf(req: AuthRequest, res: Response) { try { const user = req.user as any; if (!user?.isCustomerPortal || !user?.customerId) { return res.status(403).json({ success: false, error: 'Nur für Kundenportal-Benutzer' }); } const pdfBuffer = await consentPublicService.generateConsentPdf(user.customerId); res.setHeader('Content-Type', 'application/pdf'); res.setHeader('Content-Disposition', 'inline; filename="datenschutzerklaerung.pdf"'); res.send(pdfBuffer); } catch (error) { console.error('Fehler beim Generieren des Portal-PDFs:', error); res.status(500).json({ success: false, error: 'Fehler beim Generieren' }); } } /** * Portal: Eigenen Consent-Status prüfen */ export async function getMyConsentStatus(req: AuthRequest, res: Response) { try { const user = req.user as any; if (!user?.isCustomerPortal || !user?.customerId) { return res.status(403).json({ success: false, error: 'Nur für Kundenportal-Benutzer' }); } const result = await consentService.hasFullConsent(user.customerId); res.json({ success: true, data: result }); } catch (error) { console.error('Fehler beim Consent-Status:', error); res.status(500).json({ success: false, error: 'Fehler beim Consent-Status' }); } } export async function sendConsentLink(req: AuthRequest, res: Response) { try { const customerId = parseInt(req.params.customerId); const { channel } = req.body; // 'email', 'whatsapp', 'telegram', 'signal' // ConsentHash sicherstellen const hash = await consentPublicService.ensureConsentHash(customerId); const baseUrl = process.env.PUBLIC_URL || req.headers.origin || 'http://localhost:5173'; const consentUrl = `${baseUrl}/datenschutz/${hash}`; // Bei E-Mail: tatsächlich senden if (channel === 'email') { // Kunde laden const customer = await prisma.customer.findUnique({ where: { id: customerId }, select: { id: true, firstName: true, lastName: true, email: true }, }); if (!customer?.email) { return res.status(400).json({ success: false, error: 'Kunde hat keine E-Mail-Adresse hinterlegt', }); } // System-E-Mail-Credentials vom aktiven Provider holen const systemEmail = await getSystemEmailCredentials(); if (!systemEmail) { return res.status(400).json({ success: false, error: 'Keine System-E-Mail konfiguriert. Bitte in den Email-Provider-Einstellungen eine System-E-Mail-Adresse und Passwort hinterlegen.', }); } const credentials: SmtpCredentials = { host: systemEmail.smtpServer, port: systemEmail.smtpPort, user: systemEmail.emailAddress, password: systemEmail.password, encryption: systemEmail.smtpEncryption, allowSelfSignedCerts: systemEmail.allowSelfSignedCerts, }; // E-Mail zusammenstellen const emailHtml = `

Datenschutzerklärung – Ihre Zustimmung

Sehr geehrte(r) ${customer.firstName} ${customer.lastName},

um Sie optimal beraten und betreuen zu können, benötigen wir Ihre Zustimmung zu unserer Datenschutzerklärung.

Bitte klicken Sie auf den folgenden Button, um unsere Datenschutzerklärung einzusehen und Ihre Einwilligung zu erteilen:

Datenschutzerklärung ansehen & zustimmen

Alternativ können Sie auch diesen Link in Ihren Browser kopieren:
${consentUrl}


Hacker-Net Telekommunikation – Stefan Hacker
Am Wunderburgpark 5b, 26135 Oldenburg
info@hacker-net.de

`; const result = await sendEmail(credentials, systemEmail.emailAddress, { to: customer.email, subject: 'Datenschutzerklärung – Bitte um Ihre Zustimmung', html: emailHtml, }, { context: 'consent-link', customerId, triggeredBy: req.user?.email, }); if (!result.success) { return res.status(400).json({ success: false, error: `E-Mail-Versand fehlgeschlagen: ${result.error}`, }); } } // Audit-Log await createAuditLog({ userId: req.user?.userId, userEmail: req.user?.email || 'unknown', action: 'READ', resourceType: 'CustomerConsent', resourceId: customerId.toString(), resourceLabel: `Consent-Link gesendet (${channel})`, endpoint: req.path, httpMethod: req.method, ipAddress: req.socket.remoteAddress || 'unknown', dataSubjectId: customerId, legalBasis: 'DSGVO Art. 6 Abs. 1a', }); res.json({ success: true, data: { url: consentUrl, channel, hash, }, }); } catch (error) { console.error('Fehler beim Senden des Consent-Links:', error); res.status(500).json({ success: false, error: error instanceof Error ? error.message : 'Fehler beim Senden', }); } } // ==================== VOLLMACHTEN ==================== /** * Vollmacht-Anfrage an Kunden senden (per E-Mail, WhatsApp, Telegram, Signal) */ export async function sendAuthorizationRequest(req: AuthRequest, res: Response) { try { const customerId = parseInt(req.params.customerId); const representativeId = parseInt(req.params.representativeId); const { channel } = req.body; // Kunde (Vollmachtgeber) laden const customer = await prisma.customer.findUnique({ where: { id: customerId }, select: { id: true, firstName: true, lastName: true, email: true }, }); // Vertreter (Bevollmächtigter) laden const representative = await prisma.customer.findUnique({ where: { id: representativeId }, select: { id: true, firstName: true, lastName: true }, }); if (!customer || !representative) { return res.status(404).json({ success: false, error: 'Kunde oder Vertreter nicht gefunden' }); } const baseUrl = process.env.PUBLIC_URL || req.headers.origin || 'http://localhost:5173'; const portalUrl = `${baseUrl}/privacy`; // E-Mail senden if (channel === 'email') { if (!customer.email) { return res.status(400).json({ success: false, error: 'Kunde hat keine E-Mail-Adresse hinterlegt' }); } const systemEmail = await getSystemEmailCredentials(); if (!systemEmail) { return res.status(400).json({ success: false, error: 'Keine System-E-Mail konfiguriert. Bitte in den Email-Provider-Einstellungen eine System-E-Mail-Adresse und Passwort hinterlegen.', }); } const credentials: SmtpCredentials = { host: systemEmail.smtpServer, port: systemEmail.smtpPort, user: systemEmail.emailAddress, password: systemEmail.password, encryption: systemEmail.smtpEncryption, allowSelfSignedCerts: systemEmail.allowSelfSignedCerts, }; const emailHtml = `

Vollmacht – Ihre Zustimmung erforderlich

Sehr geehrte(r) ${customer.firstName} ${customer.lastName},

${representative.firstName} ${representative.lastName} möchte als Ihr Vertreter Zugriff auf Ihre Vertragsdaten erhalten.

Damit dies möglich ist, benötigen wir Ihre Vollmacht. Sie können diese bequem über unser Kundenportal erteilen:

Vollmacht im Portal erteilen

Alternativ können Sie auch diesen Link in Ihren Browser kopieren:
${portalUrl}

Sie können die Vollmacht jederzeit im Portal unter "Datenschutz" widerrufen.


Hacker-Net Telekommunikation – Stefan Hacker
Am Wunderburgpark 5b, 26135 Oldenburg
info@hacker-net.de

`; const result = await sendEmail(credentials, systemEmail.emailAddress, { to: customer.email, subject: `Vollmacht für ${representative.firstName} ${representative.lastName} – Bitte um Ihre Zustimmung`, html: emailHtml, }, { context: 'authorization-request', customerId, triggeredBy: req.user?.email, }); if (!result.success) { return res.status(400).json({ success: false, error: `E-Mail-Versand fehlgeschlagen: ${result.error}`, }); } } // Messaging-Text für WhatsApp/Telegram/Signal const messageText = `Hallo ${customer.firstName}, ${representative.firstName} ${representative.lastName} möchte als Ihr Vertreter Zugriff auf Ihre Vertragsdaten. Bitte erteilen Sie die Vollmacht im Portal: ${portalUrl}`; res.json({ success: true, data: { channel, portalUrl, messageText }, }); } catch (error) { console.error('Fehler beim Senden der Vollmacht-Anfrage:', error); res.status(500).json({ success: false, error: error instanceof Error ? error.message : 'Fehler beim Senden', }); } } /** * Vollmachten für einen Kunden abrufen (Admin-Ansicht) */ export async function getAuthorizations(req: AuthRequest, res: Response) { try { const customerId = parseInt(req.params.customerId); if (!(await canAccessCustomer(req, res, customerId))) return; // Sicherstellen dass Einträge für alle aktiven Vertreter existieren await authorizationService.ensureAuthorizationEntries(customerId); const authorizations = await authorizationService.getAuthorizationsForCustomer(customerId); res.json({ success: true, data: authorizations }); } catch (error) { console.error('Fehler beim Laden der Vollmachten:', error); res.status(500).json({ success: false, error: 'Fehler beim Laden der Vollmachten' }); } } /** * Vollmacht erteilen (Admin: z.B. Papier-Upload) */ export async function grantAuthorization(req: AuthRequest, res: Response) { try { const customerId = parseInt(req.params.customerId); const representativeId = parseInt(req.params.representativeId); const { source, notes } = req.body; const auth = await authorizationService.grantAuthorization(customerId, representativeId, { source: source || 'crm-backend', notes, }); const rep = await prisma.customer.findUnique({ where: { id: representativeId }, select: { firstName: true, lastName: true } }); const repName = rep ? `${rep.firstName} ${rep.lastName}` : `#${representativeId}`; await logChange({ req, action: 'UPDATE', resourceType: 'Authorization', resourceId: auth.id.toString(), label: `Vollmacht für ${repName} erteilt (Admin)`, customerId, }); res.json({ success: true, data: auth }); } catch (error) { console.error('Fehler beim Erteilen der Vollmacht:', error); res.status(400).json({ success: false, error: error instanceof Error ? error.message : 'Fehler beim Erteilen der Vollmacht', }); } } /** * Vollmacht widerrufen */ export async function withdrawAuthorization(req: AuthRequest, res: Response) { try { const customerId = parseInt(req.params.customerId); const representativeId = parseInt(req.params.representativeId); const auth = await authorizationService.withdrawAuthorization(customerId, representativeId); const rep = await prisma.customer.findUnique({ where: { id: representativeId }, select: { firstName: true, lastName: true } }); const repName = rep ? `${rep.firstName} ${rep.lastName}` : `#${representativeId}`; await logChange({ req, action: 'UPDATE', resourceType: 'Authorization', resourceId: auth.id.toString(), label: `Vollmacht für ${repName} widerrufen (Admin)`, customerId, }); res.json({ success: true, data: auth }); } catch (error) { console.error('Fehler beim Widerrufen der Vollmacht:', error); res.status(400).json({ success: false, error: error instanceof Error ? error.message : 'Fehler beim Widerrufen', }); } } /** * Vollmacht-Dokument hochladen (PDF) */ export async function uploadAuthorizationDocument(req: AuthRequest, res: Response) { try { const customerId = parseInt(req.params.customerId); const representativeId = parseInt(req.params.representativeId); if (!req.file) { return res.status(400).json({ success: false, error: 'Keine Datei hochgeladen' }); } const documentPath = `/uploads/authorizations/${req.file.filename}`; const auth = await authorizationService.updateAuthorizationDocument( customerId, representativeId, documentPath ); await logChange({ req, action: 'CREATE', resourceType: 'AuthorizationDocument', resourceId: auth.id.toString(), label: `Vollmacht-PDF hochgeladen für Vertreter #${representativeId}`, customerId, }); res.json({ success: true, data: auth }); } catch (error) { console.error('Fehler beim Upload des Vollmacht-Dokuments:', error); res.status(400).json({ success: false, error: error instanceof Error ? error.message : 'Fehler beim Upload', }); } } /** * Vollmacht-Dokument löschen */ export async function deleteAuthorizationDocument(req: AuthRequest, res: Response) { try { const customerId = parseInt(req.params.customerId); const representativeId = parseInt(req.params.representativeId); const auth = await authorizationService.deleteAuthorizationDocument(customerId, representativeId); await logChange({ req, action: 'DELETE', resourceType: 'AuthorizationDocument', resourceId: auth.id.toString(), label: `Vollmacht-PDF gelöscht für Vertreter #${representativeId}`, customerId, }); res.json({ success: true, data: auth }); } catch (error) { console.error('Fehler beim Löschen des Vollmacht-Dokuments:', error); res.status(400).json({ success: false, error: error instanceof Error ? error.message : 'Fehler beim Löschen', }); } } /** * Portal: Eigene Vollmachten abrufen (welche Vertreter dürfen meine Daten sehen?) */ export async function getMyAuthorizations(req: AuthRequest, res: Response) { try { const user = req.user as any; if (!user?.isCustomerPortal || !user?.customerId) { return res.status(403).json({ success: false, error: 'Nur für Kundenportal-Benutzer' }); } await authorizationService.ensureAuthorizationEntries(user.customerId); const authorizations = await authorizationService.getAuthorizationsForCustomer(user.customerId); res.json({ success: true, data: authorizations }); } catch (error) { console.error('Fehler beim Laden der eigenen Vollmachten:', error); res.status(500).json({ success: false, error: 'Fehler beim Laden' }); } } /** * Portal: Vollmacht erteilen/widerrufen */ export async function toggleMyAuthorization(req: AuthRequest, res: Response) { try { const user = req.user as any; if (!user?.isCustomerPortal || !user?.customerId) { return res.status(403).json({ success: false, error: 'Nur für Kundenportal-Benutzer' }); } const representativeId = parseInt(req.params.representativeId); const { grant } = req.body; // Vertreter-Name laden const representative = await prisma.customer.findUnique({ where: { id: representativeId }, select: { firstName: true, lastName: true }, }); const repName = representative ? `${representative.firstName} ${representative.lastName}` : `#${representativeId}`; let auth; if (grant) { auth = await authorizationService.grantAuthorization(user.customerId, representativeId, { source: 'portal', }); await logChange({ req, action: 'UPDATE', resourceType: 'RepresentativeAuthorization', label: `Vollmacht für ${repName} erteilt`, details: { status: 'erteilt', vertreter: repName, quelle: 'portal' }, customerId: user.customerId }); } else { auth = await authorizationService.withdrawAuthorization(user.customerId, representativeId); await logChange({ req, action: 'UPDATE', resourceType: 'RepresentativeAuthorization', label: `Vollmacht für ${repName} widerrufen`, details: { status: 'widerrufen', vertreter: repName, quelle: 'portal' }, customerId: user.customerId }); } res.json({ success: true, data: auth }); } catch (error) { console.error('Fehler beim Ändern der Vollmacht:', error); res.status(400).json({ success: false, error: error instanceof Error ? error.message : 'Fehler beim Ändern', }); } } /** * Portal: Prüfe Vollmacht-Status für alle vertretenen Kunden */ export async function getMyAuthorizationStatus(req: AuthRequest, res: Response) { try { const user = req.user as any; if (!user?.isCustomerPortal || !user?.customerId) { return res.status(403).json({ success: false, error: 'Nur für Kundenportal-Benutzer' }); } // IDs der Kunden die dieser Vertreter vertritt const representedIds: number[] = user.representedCustomerIds || []; // Für jeden vertretenen Kunden prüfen ob Vollmacht erteilt const statuses: { customerId: number; hasAuthorization: boolean }[] = []; for (const custId of representedIds) { const has = await authorizationService.hasAuthorization(custId, user.customerId); statuses.push({ customerId: custId, hasAuthorization: has }); } res.json({ success: true, data: statuses }); } catch (error) { console.error('Fehler beim Vollmacht-Status:', error); res.status(500).json({ success: false, error: 'Fehler beim Laden' }); } }