gdpr audit implemented, email log, vollmachten, pdf delete cancel data privacy and vollmachten, removed message no id card in engergy car, and other contracts that are not telecom contracts, added insert counter for engery
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
import { ConsentType, ConsentStatus } from '@prisma/client';
|
||||
import crypto from 'crypto';
|
||||
import prisma from '../lib/prisma.js';
|
||||
import * as consentService from './consent.service.js';
|
||||
import * as appSettingService from './appSetting.service.js';
|
||||
import PDFDocument from 'pdfkit';
|
||||
|
||||
/**
|
||||
* Kunden-Lookup per consentHash
|
||||
*/
|
||||
export async function getCustomerByConsentHash(hash: string) {
|
||||
const customer = await prisma.customer.findUnique({
|
||||
where: { consentHash: hash },
|
||||
select: {
|
||||
id: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
customerNumber: true,
|
||||
salutation: true,
|
||||
email: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!customer) return null;
|
||||
|
||||
const consents = await consentService.getCustomerConsents(customer.id);
|
||||
|
||||
return { customer, consents };
|
||||
}
|
||||
|
||||
/**
|
||||
* Alle 4 Einwilligungen über den öffentlichen Link erteilen
|
||||
*/
|
||||
export async function grantAllConsentsPublic(hash: string, ipAddress: string) {
|
||||
const customer = await prisma.customer.findUnique({
|
||||
where: { consentHash: hash },
|
||||
select: { id: true, firstName: true, lastName: true },
|
||||
});
|
||||
|
||||
if (!customer) {
|
||||
throw new Error('Ungültiger Link');
|
||||
}
|
||||
|
||||
const results = [];
|
||||
for (const type of Object.values(ConsentType)) {
|
||||
const result = await consentService.updateConsent(customer.id, type, {
|
||||
status: ConsentStatus.GRANTED,
|
||||
source: 'public-link',
|
||||
ipAddress,
|
||||
createdBy: `${customer.firstName} ${customer.lastName} (Public-Link)`,
|
||||
});
|
||||
results.push(result);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* consentHash generieren falls nicht vorhanden
|
||||
*/
|
||||
export async function ensureConsentHash(customerId: number): Promise<string> {
|
||||
const customer = await prisma.customer.findUnique({
|
||||
where: { id: customerId },
|
||||
select: { consentHash: true },
|
||||
});
|
||||
|
||||
if (!customer) {
|
||||
throw new Error('Kunde nicht gefunden');
|
||||
}
|
||||
|
||||
if (customer.consentHash) {
|
||||
return customer.consentHash;
|
||||
}
|
||||
|
||||
const hash = crypto.randomUUID();
|
||||
await prisma.customer.update({
|
||||
where: { id: customerId },
|
||||
data: { consentHash: hash },
|
||||
});
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Platzhalter in Text ersetzen
|
||||
*/
|
||||
function replacePlaceholders(html: string, customer: {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
customerNumber: string;
|
||||
salutation?: string | null;
|
||||
email?: string | null;
|
||||
}): string {
|
||||
return html
|
||||
.replace(/\{\{vorname\}\}/gi, customer.firstName || '')
|
||||
.replace(/\{\{nachname\}\}/gi, customer.lastName || '')
|
||||
.replace(/\{\{kundennummer\}\}/gi, customer.customerNumber || '')
|
||||
.replace(/\{\{anrede\}\}/gi, customer.salutation || '')
|
||||
.replace(/\{\{email\}\}/gi, customer.email || '')
|
||||
.replace(/\{\{datum\}\}/gi, new Date().toLocaleDateString('de-DE', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Datenschutzerklärung als HTML abrufen (mit Platzhaltern ersetzt)
|
||||
*/
|
||||
export async function getPrivacyPolicyHtml(customerId?: number): Promise<string> {
|
||||
const html = await appSettingService.getSetting('privacyPolicyHtml');
|
||||
|
||||
if (!html) {
|
||||
return '<p>Keine Datenschutzerklärung hinterlegt.</p>';
|
||||
}
|
||||
|
||||
if (!customerId) return html;
|
||||
|
||||
const customer = await prisma.customer.findUnique({
|
||||
where: { id: customerId },
|
||||
select: {
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
customerNumber: true,
|
||||
salutation: true,
|
||||
email: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!customer) return html;
|
||||
|
||||
return replacePlaceholders(html, customer);
|
||||
}
|
||||
|
||||
/**
|
||||
* HTML zu Plain-Text konvertieren (für PDF)
|
||||
*/
|
||||
function htmlToText(html: string): string {
|
||||
return html
|
||||
.replace(/<h[1-6][^>]*>(.*?)<\/h[1-6]>/gi, '\n$1\n')
|
||||
.replace(/<br\s*\/?>/gi, '\n')
|
||||
.replace(/<\/p>/gi, '\n\n')
|
||||
.replace(/<li[^>]*>(.*?)<\/li>/gi, ' • $1\n')
|
||||
.replace(/<[^>]+>/g, '')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, "'")
|
||||
.replace(/ /g, ' ')
|
||||
.replace(/\n{3,}/g, '\n\n')
|
||||
.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Datenschutzerklärung als PDF generieren
|
||||
*/
|
||||
export async function generateConsentPdf(customerId: number): Promise<Buffer> {
|
||||
const html = await getPrivacyPolicyHtml(customerId);
|
||||
const text = htmlToText(html);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const doc = new PDFDocument({ size: 'A4', margin: 50 });
|
||||
const chunks: Buffer[] = [];
|
||||
|
||||
doc.on('data', (chunk: Buffer) => chunks.push(chunk));
|
||||
doc.on('end', () => resolve(Buffer.concat(chunks)));
|
||||
doc.on('error', reject);
|
||||
|
||||
// Titel
|
||||
doc.fontSize(18).font('Helvetica-Bold').text('Datenschutzerklärung', { align: 'center' });
|
||||
doc.moveDown(1);
|
||||
|
||||
// Datum
|
||||
doc.fontSize(10).font('Helvetica')
|
||||
.text(`Erstellt am: ${new Date().toLocaleDateString('de-DE')}`, { align: 'right' });
|
||||
doc.moveDown(1);
|
||||
|
||||
// Inhalt
|
||||
doc.fontSize(11).font('Helvetica').text(text, {
|
||||
align: 'left',
|
||||
lineGap: 4,
|
||||
});
|
||||
|
||||
doc.end();
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user