complete new audit system
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import { Response } from 'express';
|
||||
import prisma from '../lib/prisma.js';
|
||||
import * as appSettingService from '../services/appSetting.service.js';
|
||||
import { logChange } from '../services/audit.service.js';
|
||||
import { ApiResponse, AuthRequest } from '../types/index.js';
|
||||
|
||||
export async function getAllSettings(req: AuthRequest, res: Response): Promise<void> {
|
||||
@@ -39,7 +41,22 @@ export async function updateSetting(req: AuthRequest, res: Response): Promise<vo
|
||||
return;
|
||||
}
|
||||
|
||||
await appSettingService.setSetting(key, String(value));
|
||||
// Vorherigen Stand laden für Audit
|
||||
const before = await prisma.appSetting.findUnique({ where: { key } });
|
||||
const oldValue = before?.value ?? '-';
|
||||
const newValue = String(value);
|
||||
|
||||
await appSettingService.setSetting(key, newValue);
|
||||
|
||||
const label = oldValue !== newValue
|
||||
? `Einstellung "${key}" geändert: ${oldValue} → ${newValue}`
|
||||
: `Einstellung "${key}" geändert`;
|
||||
await logChange({
|
||||
req, action: 'UPDATE', resourceType: 'AppSetting',
|
||||
resourceId: key,
|
||||
label,
|
||||
details: oldValue !== newValue ? { [key]: { von: oldValue, nach: newValue } } : undefined,
|
||||
});
|
||||
res.json({ success: true, message: 'Einstellung gespeichert' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -61,10 +78,27 @@ export async function updateSettings(req: AuthRequest, res: Response): Promise<v
|
||||
return;
|
||||
}
|
||||
|
||||
// Vorherige Werte laden für Audit
|
||||
const changes: Record<string, { von: unknown; nach: unknown }> = {};
|
||||
for (const [key, value] of Object.entries(settings)) {
|
||||
await appSettingService.setSetting(key, String(value));
|
||||
const before = await prisma.appSetting.findUnique({ where: { key } });
|
||||
const oldValue = before?.value ?? '-';
|
||||
const newValue = String(value);
|
||||
if (oldValue !== newValue) {
|
||||
changes[key] = { von: oldValue, nach: newValue };
|
||||
}
|
||||
await appSettingService.setSetting(key, newValue);
|
||||
}
|
||||
|
||||
const changeList = Object.entries(changes).map(([k, c]) => `${k}: ${c.von} → ${c.nach}`).join(', ');
|
||||
await logChange({
|
||||
req, action: 'UPDATE', resourceType: 'AppSetting',
|
||||
label: changeList
|
||||
? `Einstellungen aktualisiert: ${changeList}`
|
||||
: `Einstellungen aktualisiert (${Object.keys(settings).join(', ')})`,
|
||||
details: Object.keys(changes).length > 0 ? changes : undefined,
|
||||
});
|
||||
|
||||
res.json({ success: true, message: 'Einstellungen gespeichert' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Response } from 'express';
|
||||
import { AuthRequest } from '../types/index.js';
|
||||
import * as auditService from '../services/audit.service.js';
|
||||
import { logChange } from '../services/audit.service.js';
|
||||
import { AuditAction, AuditSensitivity } from '@prisma/client';
|
||||
|
||||
/**
|
||||
@@ -106,12 +107,15 @@ export async function exportAuditLogs(req: AuthRequest, res: Response) {
|
||||
format
|
||||
);
|
||||
|
||||
const contentType = format === 'csv' ? 'text/csv' : 'application/json';
|
||||
const filename = `audit-logs-${new Date().toISOString().split('T')[0]}.${format}`;
|
||||
|
||||
res.setHeader('Content-Type', contentType);
|
||||
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
|
||||
res.send(content);
|
||||
if (format === 'csv') {
|
||||
const filename = `audit-logs-${new Date().toISOString().split('T')[0]}.csv`;
|
||||
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
||||
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
|
||||
// BOM für Excel UTF-8 Erkennung
|
||||
res.send('\uFEFF' + content);
|
||||
} else {
|
||||
res.json({ success: true, data: JSON.parse(content) })
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Exportieren der Audit-Logs:', error);
|
||||
res.status(500).json({ success: false, error: 'Fehler beim Exportieren' });
|
||||
@@ -147,6 +151,23 @@ export async function verifyIntegrity(req: AuthRequest, res: Response) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash-Kette reparieren (alle Hashes neu berechnen)
|
||||
*/
|
||||
export async function rehashAll(req: AuthRequest, res: Response) {
|
||||
try {
|
||||
const result = await auditService.rehashAll();
|
||||
res.json({
|
||||
success: true,
|
||||
data: result,
|
||||
message: `${result.rehashedCount} Einträge neu gehasht. Kette ist jetzt intakt.`,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Re-Hashing:', error);
|
||||
res.status(500).json({ success: false, error: 'Fehler beim Re-Hashing' });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retention-Policies abrufen
|
||||
*/
|
||||
@@ -175,6 +196,12 @@ export async function updateRetentionPolicy(req: AuthRequest, res: Response) {
|
||||
isActive,
|
||||
});
|
||||
|
||||
await logChange({
|
||||
req, action: 'UPDATE', resourceType: 'RetentionPolicy',
|
||||
resourceId: id.toString(),
|
||||
label: `Aufbewahrungsrichtlinie aktualisiert`,
|
||||
});
|
||||
|
||||
res.json({ success: true, data: policy });
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Aktualisieren der Retention-Policy:', error);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Request, Response } from 'express';
|
||||
import * as backupService from '../services/backup.service.js';
|
||||
import { logChange } from '../services/audit.service.js';
|
||||
|
||||
/**
|
||||
* Liste aller Backups abrufen
|
||||
@@ -23,6 +24,10 @@ export async function createBackup(req: Request, res: Response) {
|
||||
const result = await backupService.createBackup();
|
||||
|
||||
if (result.success) {
|
||||
await logChange({
|
||||
req, action: 'CREATE', resourceType: 'Backup',
|
||||
label: `Backup ${result.backupName} erstellt`,
|
||||
});
|
||||
res.json({ data: { backupName: result.backupName }, message: 'Backup erfolgreich erstellt' });
|
||||
} else {
|
||||
res.status(500).json({ error: 'Backup fehlgeschlagen', details: result.error });
|
||||
@@ -47,6 +52,10 @@ export async function restoreBackup(req: Request, res: Response) {
|
||||
const result = await backupService.restoreBackup(name);
|
||||
|
||||
if (result.success) {
|
||||
await logChange({
|
||||
req, action: 'UPDATE', resourceType: 'Backup',
|
||||
label: `Backup ${name} wiederhergestellt`,
|
||||
});
|
||||
res.json({
|
||||
data: {
|
||||
restoredRecords: result.restoredRecords,
|
||||
@@ -77,6 +86,10 @@ export async function deleteBackup(req: Request, res: Response) {
|
||||
const result = await backupService.deleteBackup(name);
|
||||
|
||||
if (result.success) {
|
||||
await logChange({
|
||||
req, action: 'DELETE', resourceType: 'Backup',
|
||||
label: `Backup ${name} gelöscht`,
|
||||
});
|
||||
res.json({ message: 'Backup gelöscht' });
|
||||
} else {
|
||||
res.status(500).json({ error: 'Löschen fehlgeschlagen', details: result.error });
|
||||
@@ -157,6 +170,10 @@ export async function factoryReset(req: Request, res: Response) {
|
||||
const result = await backupService.factoryReset();
|
||||
|
||||
if (result.success) {
|
||||
await logChange({
|
||||
req, action: 'DELETE', resourceType: 'System',
|
||||
label: `Werkseinstellungen wiederhergestellt`,
|
||||
});
|
||||
res.json({
|
||||
message: 'Werkseinstellungen wiederhergestellt. Bitte melden Sie sich mit admin@admin.com / admin an.',
|
||||
});
|
||||
|
||||
@@ -11,12 +11,11 @@ import { decrypt } from '../utils/encryption.js';
|
||||
import { ApiResponse } from '../types/index.js';
|
||||
import { getCustomerTargets, getContractTargets, getIdentityDocumentTargets, getBankCardTargets, documentTargets } from '../config/documentTargets.config.js';
|
||||
import { generateEmailPdf } from '../services/pdfService.js';
|
||||
import { PrismaClient, DocumentType } from '@prisma/client';
|
||||
import { DocumentType } from '@prisma/client';
|
||||
import prisma from '../lib/prisma.js';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// ==================== E-MAIL LIST ====================
|
||||
|
||||
// E-Mails für einen Kunden abrufen
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Request, Response } from 'express';
|
||||
import * as cancellationPeriodService from '../services/cancellation-period.service.js';
|
||||
import { logChange } from '../services/audit.service.js';
|
||||
import { ApiResponse } from '../types/index.js';
|
||||
|
||||
export async function getCancellationPeriods(req: Request, res: Response): Promise<void> {
|
||||
@@ -37,6 +38,11 @@ export async function getCancellationPeriod(req: Request, res: Response): Promis
|
||||
export async function createCancellationPeriod(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const period = await cancellationPeriodService.createCancellationPeriod(req.body);
|
||||
await logChange({
|
||||
req, action: 'CREATE', resourceType: 'CancellationPeriod',
|
||||
resourceId: period.id.toString(),
|
||||
label: `Kündigungsfrist ${period.description} angelegt`,
|
||||
});
|
||||
res.status(201).json({ success: true, data: period } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -49,6 +55,11 @@ export async function createCancellationPeriod(req: Request, res: Response): Pro
|
||||
export async function updateCancellationPeriod(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const period = await cancellationPeriodService.updateCancellationPeriod(parseInt(req.params.id), req.body);
|
||||
await logChange({
|
||||
req, action: 'UPDATE', resourceType: 'CancellationPeriod',
|
||||
resourceId: period.id.toString(),
|
||||
label: `Kündigungsfrist ${period.description} aktualisiert`,
|
||||
});
|
||||
res.json({ success: true, data: period } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -60,7 +71,14 @@ export async function updateCancellationPeriod(req: Request, res: Response): Pro
|
||||
|
||||
export async function deleteCancellationPeriod(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
await cancellationPeriodService.deleteCancellationPeriod(parseInt(req.params.id));
|
||||
const periodId = parseInt(req.params.id);
|
||||
const period = await cancellationPeriodService.getCancellationPeriodById(periodId);
|
||||
await cancellationPeriodService.deleteCancellationPeriod(periodId);
|
||||
await logChange({
|
||||
req, action: 'DELETE', resourceType: 'CancellationPeriod',
|
||||
resourceId: periodId.toString(),
|
||||
label: `Kündigungsfrist ${period?.description || periodId} gelöscht`,
|
||||
});
|
||||
res.json({ success: true, message: 'Kündigungsfrist gelöscht' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Request, Response } from 'express';
|
||||
import * as contractDurationService from '../services/contract-duration.service.js';
|
||||
import { logChange } from '../services/audit.service.js';
|
||||
import { ApiResponse } from '../types/index.js';
|
||||
|
||||
export async function getContractDurations(req: Request, res: Response): Promise<void> {
|
||||
@@ -37,6 +38,11 @@ export async function getContractDuration(req: Request, res: Response): Promise<
|
||||
export async function createContractDuration(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const duration = await contractDurationService.createContractDuration(req.body);
|
||||
await logChange({
|
||||
req, action: 'CREATE', resourceType: 'ContractDuration',
|
||||
resourceId: duration.id.toString(),
|
||||
label: `Laufzeit ${duration.description} angelegt`,
|
||||
});
|
||||
res.status(201).json({ success: true, data: duration } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -49,6 +55,11 @@ export async function createContractDuration(req: Request, res: Response): Promi
|
||||
export async function updateContractDuration(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const duration = await contractDurationService.updateContractDuration(parseInt(req.params.id), req.body);
|
||||
await logChange({
|
||||
req, action: 'UPDATE', resourceType: 'ContractDuration',
|
||||
resourceId: duration.id.toString(),
|
||||
label: `Laufzeit ${duration.description} aktualisiert`,
|
||||
});
|
||||
res.json({ success: true, data: duration } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -60,7 +71,14 @@ export async function updateContractDuration(req: Request, res: Response): Promi
|
||||
|
||||
export async function deleteContractDuration(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
await contractDurationService.deleteContractDuration(parseInt(req.params.id));
|
||||
const durationId = parseInt(req.params.id);
|
||||
const duration = await contractDurationService.getContractDurationById(durationId);
|
||||
await contractDurationService.deleteContractDuration(durationId);
|
||||
await logChange({
|
||||
req, action: 'DELETE', resourceType: 'ContractDuration',
|
||||
resourceId: durationId.toString(),
|
||||
label: `Laufzeit ${duration?.description || durationId} gelöscht`,
|
||||
});
|
||||
res.json({ success: true, message: 'Laufzeit gelöscht' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import prisma from '../lib/prisma.js';
|
||||
import * as contractService from '../services/contract.service.js';
|
||||
import * as contractCockpitService from '../services/contractCockpit.service.js';
|
||||
import * as contractHistoryService from '../services/contractHistory.service.js';
|
||||
import * as authorizationService from '../services/authorization.service.js';
|
||||
import { ApiResponse, AuthRequest } from '../types/index.js';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
import { logChange } from '../services/audit.service.js';
|
||||
|
||||
export async function getContracts(req: AuthRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
@@ -100,6 +99,12 @@ export async function getContract(req: AuthRequest, res: Response): Promise<void
|
||||
export async function createContract(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const contract = await contractService.createContract(req.body);
|
||||
await logChange({
|
||||
req, action: 'CREATE', resourceType: 'Contract',
|
||||
resourceId: contract.id.toString(),
|
||||
label: `Vertrag ${contract.contractNumber} angelegt`,
|
||||
customerId: contract.customerId,
|
||||
});
|
||||
res.status(201).json({ success: true, data: contract } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -109,9 +114,69 @@ export async function createContract(req: Request, res: Response): Promise<void>
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateContract(req: Request, res: Response): Promise<void> {
|
||||
export async function updateContract(req: AuthRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
const contract = await contractService.updateContract(parseInt(req.params.id), req.body);
|
||||
const contractId = parseInt(req.params.id);
|
||||
// Vorherigen Stand laden für Audit-Vergleich
|
||||
const before = await prisma.contract.findUnique({
|
||||
where: { id: contractId },
|
||||
include: { energyDetails: true, internetDetails: true, mobileDetails: true, tvDetails: true, carInsuranceDetails: true },
|
||||
});
|
||||
|
||||
const contract = await contractService.updateContract(contractId, req.body);
|
||||
|
||||
// Geänderte Felder ermitteln
|
||||
const changes: Record<string, { von: unknown; nach: unknown }> = {};
|
||||
const fieldLabels: Record<string, string> = {
|
||||
status: 'Status', startDate: 'Vertragsbeginn', endDate: 'Vertragsende',
|
||||
portalUsername: 'Portal-Benutzername', customerNumberAtProvider: 'Kundennummer beim Anbieter',
|
||||
providerId: 'Anbieter', tariffId: 'Tarif', cancellationPeriodId: 'Kündigungsfrist',
|
||||
contractDurationId: 'Vertragslaufzeit', platformId: 'Vertriebsplattform',
|
||||
cancellationDate: 'Kündigungsdatum', cancellationSentDate: 'Kündigung gesendet am',
|
||||
identityDocumentId: 'Ausweis', bankCardId: 'Bankverbindung', addressId: 'Adresse',
|
||||
commission: 'Provision', notes: 'Notizen',
|
||||
};
|
||||
const energyLabels: Record<string, string> = {
|
||||
meterId: 'Zähler', maloId: 'MaLo-ID', annualConsumption: 'Jahresverbrauch',
|
||||
basePrice: 'Grundpreis', unitPrice: 'Arbeitspreis', unitPriceNt: 'NT-Arbeitspreis', bonus: 'Bonus',
|
||||
};
|
||||
|
||||
// Hauptfelder vergleichen
|
||||
const body = req.body;
|
||||
if (before) {
|
||||
for (const [key, newVal] of Object.entries(body)) {
|
||||
if (['energyDetails', 'internetDetails', 'mobileDetails', 'tvDetails', 'carInsuranceDetails', 'password'].includes(key)) continue;
|
||||
const oldVal = (before as any)[key];
|
||||
const norm = (v: unknown) => (v === null || v === undefined || v === '' ? null : v);
|
||||
if (JSON.stringify(norm(oldVal)) !== JSON.stringify(norm(newVal))) {
|
||||
const label = fieldLabels[key] || key;
|
||||
changes[label] = { von: oldVal ?? '-', nach: newVal ?? '-' };
|
||||
}
|
||||
}
|
||||
// Energie-Details vergleichen
|
||||
if (body.energyDetails && before.energyDetails) {
|
||||
for (const [key, newVal] of Object.entries(body.energyDetails)) {
|
||||
const oldVal = (before.energyDetails as any)[key];
|
||||
const norm = (v: unknown) => (v === null || v === undefined || v === '' ? null : v);
|
||||
if (JSON.stringify(norm(oldVal)) !== JSON.stringify(norm(newVal))) {
|
||||
const label = energyLabels[key] || key;
|
||||
changes[label] = { von: oldVal ?? '-', nach: newVal ?? '-' };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const changeList = Object.entries(changes).map(([f, c]) => `${f}: ${c.von} → ${c.nach}`).join(', ');
|
||||
await logChange({
|
||||
req, action: 'UPDATE', resourceType: 'Contract',
|
||||
resourceId: contractId.toString(),
|
||||
label: changeList
|
||||
? `Vertrag ${before?.contractNumber || contractId} aktualisiert: ${changeList}`
|
||||
: `Vertrag ${before?.contractNumber || contractId} aktualisiert`,
|
||||
details: Object.keys(changes).length > 0 ? changes : undefined,
|
||||
customerId: before?.customerId,
|
||||
});
|
||||
|
||||
res.json({ success: true, data: contract } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -123,7 +188,15 @@ export async function updateContract(req: Request, res: Response): Promise<void>
|
||||
|
||||
export async function deleteContract(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
await contractService.deleteContract(parseInt(req.params.id));
|
||||
const contractId = parseInt(req.params.id);
|
||||
const contract = await prisma.contract.findUnique({ where: { id: contractId }, select: { contractNumber: true, customerId: true } });
|
||||
await contractService.deleteContract(contractId);
|
||||
await logChange({
|
||||
req, action: 'DELETE', resourceType: 'Contract',
|
||||
resourceId: contractId.toString(),
|
||||
label: `Vertrag ${contract?.contractNumber} gelöscht`,
|
||||
customerId: contract?.customerId,
|
||||
});
|
||||
res.json({ success: true, message: 'Vertrag gelöscht' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -165,6 +238,13 @@ export async function createFollowUp(req: AuthRequest, res: Response): Promise<v
|
||||
createdBy
|
||||
);
|
||||
|
||||
await logChange({
|
||||
req, action: 'CREATE', resourceType: 'Contract',
|
||||
resourceId: contract.id.toString(),
|
||||
label: `Folgevertrag erstellt für ${previousContract.contractNumber}`,
|
||||
customerId: contract.customerId,
|
||||
});
|
||||
|
||||
res.status(201).json({ success: true, data: contract } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -295,6 +375,13 @@ export async function addSuccessorMeter(req: AuthRequest, res: Response): Promis
|
||||
data: { meterId: parseInt(meterId) },
|
||||
});
|
||||
|
||||
await logChange({
|
||||
req, action: 'CREATE', resourceType: 'ContractMeter',
|
||||
resourceId: contractMeter.id.toString(),
|
||||
label: `Folgezähler hinzugefügt zu Vertrag #${contractId}`,
|
||||
customerId: contract.customerId,
|
||||
});
|
||||
|
||||
res.json({ success: true, data: contractMeter } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -307,7 +394,13 @@ export async function addSuccessorMeter(req: AuthRequest, res: Response): Promis
|
||||
export async function removeContractMeter(req: AuthRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
const contractMeterId = parseInt(req.params.contractMeterId);
|
||||
const contractId = parseInt(req.params.id);
|
||||
await prisma.contractMeter.delete({ where: { id: contractMeterId } });
|
||||
await logChange({
|
||||
req, action: 'DELETE', resourceType: 'ContractMeter',
|
||||
resourceId: contractMeterId.toString(),
|
||||
label: `Folgezähler entfernt von Vertrag #${contractId}`,
|
||||
});
|
||||
res.json({ success: true, data: null } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -346,6 +439,12 @@ export async function snoozeContract(req: Request, res: Response): Promise<void>
|
||||
},
|
||||
});
|
||||
|
||||
await logChange({
|
||||
req, action: 'UPDATE', resourceType: 'Contract',
|
||||
resourceId: id.toString(),
|
||||
label: `Vertrag ${updated.contractNumber} zurückgestellt`,
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: updated,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Request, Response } from 'express';
|
||||
import * as contractCategoryService from '../services/contractCategory.service.js';
|
||||
import { logChange } from '../services/audit.service.js';
|
||||
import { ApiResponse } from '../types/index.js';
|
||||
|
||||
export async function getContractCategories(req: Request, res: Response): Promise<void> {
|
||||
@@ -37,6 +38,11 @@ export async function getContractCategory(req: Request, res: Response): Promise<
|
||||
export async function createContractCategory(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const category = await contractCategoryService.createContractCategory(req.body);
|
||||
await logChange({
|
||||
req, action: 'CREATE', resourceType: 'ContractCategory',
|
||||
resourceId: category.id.toString(),
|
||||
label: `Vertragskategorie ${category.name} angelegt`,
|
||||
});
|
||||
res.status(201).json({ success: true, data: category } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -49,6 +55,11 @@ export async function createContractCategory(req: Request, res: Response): Promi
|
||||
export async function updateContractCategory(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const category = await contractCategoryService.updateContractCategory(parseInt(req.params.id), req.body);
|
||||
await logChange({
|
||||
req, action: 'UPDATE', resourceType: 'ContractCategory',
|
||||
resourceId: category.id.toString(),
|
||||
label: `Vertragskategorie ${category.name} aktualisiert`,
|
||||
});
|
||||
res.json({ success: true, data: category } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -60,7 +71,14 @@ export async function updateContractCategory(req: Request, res: Response): Promi
|
||||
|
||||
export async function deleteContractCategory(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
await contractCategoryService.deleteContractCategory(parseInt(req.params.id));
|
||||
const categoryId = parseInt(req.params.id);
|
||||
const category = await contractCategoryService.getContractCategoryById(categoryId);
|
||||
await contractCategoryService.deleteContractCategory(categoryId);
|
||||
await logChange({
|
||||
req, action: 'DELETE', resourceType: 'ContractCategory',
|
||||
resourceId: categoryId.toString(),
|
||||
label: `Vertragskategorie ${category?.name || categoryId} gelöscht`,
|
||||
});
|
||||
res.json({ success: true, message: 'Vertragskategorie gelöscht' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Request, Response } from 'express';
|
||||
import * as contractHistoryService from '../services/contractHistory.service.js';
|
||||
import { logChange } from '../services/audit.service.js';
|
||||
import { ApiResponse, AuthRequest } from '../types/index.js';
|
||||
|
||||
export async function getHistoryEntries(req: AuthRequest, res: Response): Promise<void> {
|
||||
@@ -35,6 +36,12 @@ export async function createHistoryEntry(req: AuthRequest, res: Response): Promi
|
||||
createdBy: req.user?.email || 'unbekannt',
|
||||
});
|
||||
|
||||
await logChange({
|
||||
req, action: 'CREATE', resourceType: 'ContractHistory',
|
||||
resourceId: entry.id.toString(),
|
||||
label: `Historieneintrag "${title.trim()}" erstellt für Vertrag #${contractId}`,
|
||||
});
|
||||
|
||||
res.status(201).json({ success: true, data: entry } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -55,6 +62,12 @@ export async function updateHistoryEntry(req: AuthRequest, res: Response): Promi
|
||||
description: description?.trim(),
|
||||
});
|
||||
|
||||
await logChange({
|
||||
req, action: 'UPDATE', resourceType: 'ContractHistory',
|
||||
resourceId: entryId.toString(),
|
||||
label: `Historieneintrag aktualisiert für Vertrag #${contractId}`,
|
||||
});
|
||||
|
||||
res.json({ success: true, data: entry } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -71,6 +84,12 @@ export async function deleteHistoryEntry(req: AuthRequest, res: Response): Promi
|
||||
|
||||
await contractHistoryService.deleteHistoryEntry(contractId, entryId);
|
||||
|
||||
await logChange({
|
||||
req, action: 'DELETE', resourceType: 'ContractHistory',
|
||||
resourceId: entryId.toString(),
|
||||
label: `Historieneintrag gelöscht für Vertrag #${contractId}`,
|
||||
});
|
||||
|
||||
res.json({ success: true, message: 'Eintrag gelöscht' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
|
||||
@@ -3,6 +3,7 @@ import * as contractTaskService from '../services/contractTask.service.js';
|
||||
import * as contractService from '../services/contract.service.js';
|
||||
import * as customerService from '../services/customer.service.js';
|
||||
import * as appSettingService from '../services/appSetting.service.js';
|
||||
import { logChange } from '../services/audit.service.js';
|
||||
import { ApiResponse, AuthRequest } from '../types/index.js';
|
||||
|
||||
// ==================== ALL TASKS (Dashboard & Task List) ====================
|
||||
@@ -147,6 +148,12 @@ export async function createTask(req: AuthRequest, res: Response): Promise<void>
|
||||
createdBy,
|
||||
});
|
||||
|
||||
await logChange({
|
||||
req, action: 'CREATE', resourceType: 'ContractTask',
|
||||
resourceId: task.id.toString(),
|
||||
label: `Aufgabe "${title}" erstellt`,
|
||||
});
|
||||
|
||||
res.status(201).json({ success: true, data: task } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -212,6 +219,12 @@ export async function createSupportTicket(req: AuthRequest, res: Response): Prom
|
||||
createdBy,
|
||||
});
|
||||
|
||||
await logChange({
|
||||
req, action: 'CREATE', resourceType: 'ContractTask',
|
||||
resourceId: task.id.toString(),
|
||||
label: `Support-Anfrage "${title}" erstellt`,
|
||||
});
|
||||
|
||||
res.status(201).json({ success: true, data: task } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -232,6 +245,12 @@ export async function updateTask(req: AuthRequest, res: Response): Promise<void>
|
||||
visibleInPortal,
|
||||
});
|
||||
|
||||
await logChange({
|
||||
req, action: 'UPDATE', resourceType: 'ContractTask',
|
||||
resourceId: taskId.toString(),
|
||||
label: `Aufgabe aktualisiert`,
|
||||
});
|
||||
|
||||
res.json({ success: true, data: task } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -245,6 +264,11 @@ export async function completeTask(req: AuthRequest, res: Response): Promise<voi
|
||||
try {
|
||||
const taskId = parseInt(req.params.taskId);
|
||||
const task = await contractTaskService.completeTask(taskId);
|
||||
await logChange({
|
||||
req, action: 'UPDATE', resourceType: 'ContractTask',
|
||||
resourceId: taskId.toString(),
|
||||
label: `Aufgabe abgeschlossen`,
|
||||
});
|
||||
res.json({ success: true, data: task } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -258,6 +282,11 @@ export async function reopenTask(req: AuthRequest, res: Response): Promise<void>
|
||||
try {
|
||||
const taskId = parseInt(req.params.taskId);
|
||||
const task = await contractTaskService.reopenTask(taskId);
|
||||
await logChange({
|
||||
req, action: 'UPDATE', resourceType: 'ContractTask',
|
||||
resourceId: taskId.toString(),
|
||||
label: `Aufgabe wiedereröffnet`,
|
||||
});
|
||||
res.json({ success: true, data: task } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -271,6 +300,11 @@ export async function deleteTask(req: AuthRequest, res: Response): Promise<void>
|
||||
try {
|
||||
const taskId = parseInt(req.params.taskId);
|
||||
await contractTaskService.deleteTask(taskId);
|
||||
await logChange({
|
||||
req, action: 'DELETE', resourceType: 'ContractTask',
|
||||
resourceId: taskId.toString(),
|
||||
label: `Aufgabe gelöscht`,
|
||||
});
|
||||
res.json({ success: true, message: 'Aufgabe gelöscht' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -303,6 +337,12 @@ export async function createSubtask(req: AuthRequest, res: Response): Promise<vo
|
||||
createdBy,
|
||||
});
|
||||
|
||||
await logChange({
|
||||
req, action: 'CREATE', resourceType: 'ContractSubtask',
|
||||
resourceId: subtask.id.toString(),
|
||||
label: `Unteraufgabe "${title}" erstellt`,
|
||||
});
|
||||
|
||||
res.status(201).json({ success: true, data: subtask } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -369,6 +409,12 @@ export async function createCustomerReply(req: AuthRequest, res: Response): Prom
|
||||
createdBy,
|
||||
});
|
||||
|
||||
await logChange({
|
||||
req, action: 'CREATE', resourceType: 'ContractSubtask',
|
||||
resourceId: subtask.id.toString(),
|
||||
label: `Kundenantwort erstellt`,
|
||||
});
|
||||
|
||||
res.status(201).json({ success: true, data: subtask } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -392,6 +438,11 @@ export async function updateSubtask(req: AuthRequest, res: Response): Promise<vo
|
||||
}
|
||||
|
||||
const subtask = await contractTaskService.updateSubtask(subtaskId, { title });
|
||||
await logChange({
|
||||
req, action: 'UPDATE', resourceType: 'ContractSubtask',
|
||||
resourceId: subtaskId.toString(),
|
||||
label: `Unteraufgabe aktualisiert`,
|
||||
});
|
||||
res.json({ success: true, data: subtask } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -405,6 +456,11 @@ export async function completeSubtask(req: AuthRequest, res: Response): Promise<
|
||||
try {
|
||||
const subtaskId = parseInt(req.params.subtaskId);
|
||||
const subtask = await contractTaskService.completeSubtask(subtaskId);
|
||||
await logChange({
|
||||
req, action: 'UPDATE', resourceType: 'ContractSubtask',
|
||||
resourceId: subtaskId.toString(),
|
||||
label: `Unteraufgabe abgeschlossen`,
|
||||
});
|
||||
res.json({ success: true, data: subtask } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -418,6 +474,11 @@ export async function reopenSubtask(req: AuthRequest, res: Response): Promise<vo
|
||||
try {
|
||||
const subtaskId = parseInt(req.params.subtaskId);
|
||||
const subtask = await contractTaskService.reopenSubtask(subtaskId);
|
||||
await logChange({
|
||||
req, action: 'UPDATE', resourceType: 'ContractSubtask',
|
||||
resourceId: subtaskId.toString(),
|
||||
label: `Unteraufgabe wiedereröffnet`,
|
||||
});
|
||||
res.json({ success: true, data: subtask } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -431,6 +492,11 @@ export async function deleteSubtask(req: AuthRequest, res: Response): Promise<vo
|
||||
try {
|
||||
const subtaskId = parseInt(req.params.subtaskId);
|
||||
await contractTaskService.deleteSubtask(subtaskId);
|
||||
await logChange({
|
||||
req, action: 'DELETE', resourceType: 'ContractSubtask',
|
||||
resourceId: subtaskId.toString(),
|
||||
label: `Unteraufgabe gelöscht`,
|
||||
});
|
||||
res.json({ success: true, message: 'Unteraufgabe gelöscht' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import prisma from '../lib/prisma.js';
|
||||
import * as customerService from '../services/customer.service.js';
|
||||
import * as authService from '../services/auth.service.js';
|
||||
import { logChange } from '../services/audit.service.js';
|
||||
import { ApiResponse, AuthRequest } from '../types/index.js';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// Customer CRUD
|
||||
export async function getCustomers(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
@@ -46,6 +45,12 @@ export async function createCustomer(req: Request, res: Response): Promise<void>
|
||||
data.birthDate = new Date(data.birthDate);
|
||||
}
|
||||
const customer = await customerService.createCustomer(data);
|
||||
await logChange({
|
||||
req, action: 'CREATE', resourceType: 'Customer',
|
||||
resourceId: customer.id.toString(),
|
||||
label: `Kunde ${customer.customerNumber} angelegt (${customer.firstName} ${customer.lastName})`,
|
||||
customerId: customer.id,
|
||||
});
|
||||
res.status(201).json({ success: true, data: customer } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -57,12 +62,70 @@ export async function createCustomer(req: Request, res: Response): Promise<void>
|
||||
|
||||
export async function updateCustomer(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const customerId = parseInt(req.params.id);
|
||||
const data = { ...req.body };
|
||||
// Convert birthDate string to Date if present
|
||||
if (data.birthDate) {
|
||||
|
||||
// Vorherigen Stand laden für Audit
|
||||
const before = await prisma.customer.findUnique({ where: { id: customerId } });
|
||||
|
||||
// Convert birthDate string to Date if present, empty string to null
|
||||
if (data.birthDate === '' || data.birthDate === null) {
|
||||
data.birthDate = null;
|
||||
} else if (data.birthDate) {
|
||||
data.birthDate = new Date(data.birthDate);
|
||||
}
|
||||
const customer = await customerService.updateCustomer(parseInt(req.params.id), data);
|
||||
// Leere Strings in optionalen Feldern zu null konvertieren
|
||||
const nullableFields = ['salutation', 'birthPlace', 'phone', 'mobile', 'email', 'companyName', 'taxNumber', 'businessRegistration', 'commercialRegister', 'commercialRegisterNumber', 'notes'];
|
||||
for (const field of nullableFields) {
|
||||
if (data[field] === '') data[field] = null;
|
||||
}
|
||||
const customer = await customerService.updateCustomer(customerId, data);
|
||||
|
||||
// Audit: Geänderte Felder ermitteln und loggen
|
||||
if (before) {
|
||||
const changes: Record<string, { von: unknown; nach: unknown }> = {};
|
||||
const fieldLabels: Record<string, string> = {
|
||||
salutation: 'Anrede', firstName: 'Vorname', lastName: 'Nachname', email: 'E-Mail',
|
||||
phone: 'Telefon', mobile: 'Mobil', birthDate: 'Geburtsdatum', birthPlace: 'Geburtsort',
|
||||
companyName: 'Firma', type: 'Typ', taxNumber: 'Steuernummer', notes: 'Notizen',
|
||||
};
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
// Technische/interne Felder überspringen
|
||||
if (['id', 'createdAt', 'updatedAt', 'customerNumber', 'portalPasswordHash', 'portalPasswordEncrypted'].includes(key)) continue;
|
||||
|
||||
const oldVal = (before as any)[key];
|
||||
const newVal = value;
|
||||
// Normalisieren: null, undefined, "" werden alle als "leer" behandelt
|
||||
const normalize = (v: unknown) => {
|
||||
if (v === null || v === undefined || v === '') return null;
|
||||
if (v instanceof Date) return v.toISOString().split('T')[0];
|
||||
return v;
|
||||
};
|
||||
const oldNorm = normalize(oldVal);
|
||||
const newNorm = normalize(newVal);
|
||||
if (JSON.stringify(oldNorm) !== JSON.stringify(newNorm)) {
|
||||
const label = fieldLabels[key] || key;
|
||||
const formatVal = (v: unknown) => {
|
||||
if (v === null || v === undefined || v === '') return '-';
|
||||
if (v instanceof Date) return v.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' });
|
||||
if (typeof v === 'boolean') return v ? 'Ja' : 'Nein';
|
||||
return String(v);
|
||||
};
|
||||
changes[label] = { von: formatVal(oldVal), nach: formatVal(newVal) };
|
||||
}
|
||||
}
|
||||
if (Object.keys(changes).length > 0) {
|
||||
const changeList = Object.entries(changes).map(([f, c]) => `${f}: ${c.von} → ${c.nach}`).join(', ');
|
||||
await logChange({
|
||||
req, action: 'UPDATE', resourceType: 'Customer',
|
||||
resourceId: customerId.toString(),
|
||||
label: `Kunde ${before.customerNumber} aktualisiert: ${changeList}`,
|
||||
details: changes,
|
||||
customerId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
res.json({ success: true, data: customer } as ApiResponse);
|
||||
} catch (error) {
|
||||
console.error('Update customer error:', error);
|
||||
@@ -75,7 +138,15 @@ export async function updateCustomer(req: Request, res: Response): Promise<void>
|
||||
|
||||
export async function deleteCustomer(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
await customerService.deleteCustomer(parseInt(req.params.id));
|
||||
const customerId = parseInt(req.params.id);
|
||||
const customer = await prisma.customer.findUnique({ where: { id: customerId }, select: { customerNumber: true, firstName: true, lastName: true } });
|
||||
await customerService.deleteCustomer(customerId);
|
||||
await logChange({
|
||||
req, action: 'DELETE', resourceType: 'Customer',
|
||||
resourceId: customerId.toString(),
|
||||
label: `Kunde ${customer?.customerNumber} gelöscht (${customer?.firstName} ${customer?.lastName})`,
|
||||
customerId,
|
||||
});
|
||||
res.json({ success: true, message: 'Kunde gelöscht' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -97,7 +168,14 @@ export async function getAddresses(req: Request, res: Response): Promise<void> {
|
||||
|
||||
export async function createAddress(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const address = await customerService.createAddress(parseInt(req.params.customerId), req.body);
|
||||
const customerId = parseInt(req.params.customerId);
|
||||
const address = await customerService.createAddress(customerId, req.body);
|
||||
await logChange({
|
||||
req, action: 'CREATE', resourceType: 'Address',
|
||||
resourceId: address.id.toString(),
|
||||
label: `Adresse hinzugefügt für Kunde #${customerId}`,
|
||||
customerId,
|
||||
});
|
||||
res.status(201).json({ success: true, data: address } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -109,7 +187,53 @@ export async function createAddress(req: Request, res: Response): Promise<void>
|
||||
|
||||
export async function updateAddress(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const address = await customerService.updateAddress(parseInt(req.params.id), req.body);
|
||||
const addressId = parseInt(req.params.id);
|
||||
const data = req.body;
|
||||
|
||||
// Vorherigen Stand laden für Audit
|
||||
const before = await prisma.address.findUnique({ where: { id: addressId } });
|
||||
|
||||
const address = await customerService.updateAddress(addressId, data);
|
||||
const customerId = address.customerId;
|
||||
|
||||
// Audit: Geänderte Felder ermitteln und loggen
|
||||
if (before) {
|
||||
const changes: Record<string, { von: unknown; nach: unknown }> = {};
|
||||
const fieldLabels: Record<string, string> = {
|
||||
street: 'Straße', houseNumber: 'Hausnummer', postalCode: 'PLZ',
|
||||
city: 'Stadt', country: 'Land', type: 'Typ', isDefault: 'Standard',
|
||||
};
|
||||
for (const [key, newVal] of Object.entries(data)) {
|
||||
if (['id', 'createdAt', 'updatedAt'].includes(key)) continue;
|
||||
const oldVal = (before as any)[key];
|
||||
const norm = (v: unknown) => (v === null || v === undefined || v === '' ? null : v);
|
||||
if (JSON.stringify(norm(oldVal)) !== JSON.stringify(norm(newVal))) {
|
||||
const label = fieldLabels[key] || key;
|
||||
const formatVal = (v: unknown) => {
|
||||
if (v === null || v === undefined || v === '') return '-';
|
||||
if (typeof v === 'boolean') return v ? 'Ja' : 'Nein';
|
||||
return String(v);
|
||||
};
|
||||
changes[label] = { von: formatVal(oldVal), nach: formatVal(newVal) };
|
||||
}
|
||||
}
|
||||
const changeList = Object.entries(changes).map(([f, c]) => `${f}: ${c.von} → ${c.nach}`).join(', ');
|
||||
await logChange({
|
||||
req, action: 'UPDATE', resourceType: 'Address',
|
||||
resourceId: address.id.toString(),
|
||||
label: changeList ? `Adresse aktualisiert für Kunde #${customerId}: ${changeList}` : `Adresse aktualisiert für Kunde #${customerId}`,
|
||||
details: Object.keys(changes).length > 0 ? changes : undefined,
|
||||
customerId,
|
||||
});
|
||||
} else {
|
||||
await logChange({
|
||||
req, action: 'UPDATE', resourceType: 'Address',
|
||||
resourceId: address.id.toString(),
|
||||
label: `Adresse aktualisiert für Kunde #${customerId}`,
|
||||
customerId,
|
||||
});
|
||||
}
|
||||
|
||||
res.json({ success: true, data: address } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -121,7 +245,16 @@ export async function updateAddress(req: Request, res: Response): Promise<void>
|
||||
|
||||
export async function deleteAddress(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
await customerService.deleteAddress(parseInt(req.params.id));
|
||||
const addressId = parseInt(req.params.id);
|
||||
const addr = await prisma.address.findUnique({ where: { id: addressId }, select: { customerId: true } });
|
||||
const customerId = addr?.customerId;
|
||||
await customerService.deleteAddress(addressId);
|
||||
await logChange({
|
||||
req, action: 'DELETE', resourceType: 'Address',
|
||||
resourceId: addressId.toString(),
|
||||
label: `Adresse gelöscht für Kunde #${customerId}`,
|
||||
customerId: customerId ?? undefined,
|
||||
});
|
||||
res.json({ success: true, message: 'Adresse gelöscht' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -147,7 +280,14 @@ export async function getBankCards(req: Request, res: Response): Promise<void> {
|
||||
|
||||
export async function createBankCard(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const card = await customerService.createBankCard(parseInt(req.params.customerId), req.body);
|
||||
const customerId = parseInt(req.params.customerId);
|
||||
const card = await customerService.createBankCard(customerId, req.body);
|
||||
await logChange({
|
||||
req, action: 'CREATE', resourceType: 'BankCard',
|
||||
resourceId: card.id.toString(),
|
||||
label: `Bankverbindung hinzugefügt für Kunde #${customerId}`,
|
||||
customerId,
|
||||
});
|
||||
res.status(201).json({ success: true, data: card } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -159,7 +299,53 @@ export async function createBankCard(req: Request, res: Response): Promise<void>
|
||||
|
||||
export async function updateBankCard(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const card = await customerService.updateBankCard(parseInt(req.params.id), req.body);
|
||||
const cardId = parseInt(req.params.id);
|
||||
const data = req.body;
|
||||
|
||||
// Vorherigen Stand laden für Audit
|
||||
const before = await prisma.bankCard.findUnique({ where: { id: cardId } });
|
||||
|
||||
const card = await customerService.updateBankCard(cardId, data);
|
||||
const customerId = card.customerId;
|
||||
|
||||
// Audit: Geänderte Felder ermitteln und loggen
|
||||
if (before) {
|
||||
const changes: Record<string, { von: unknown; nach: unknown }> = {};
|
||||
const fieldLabels: Record<string, string> = {
|
||||
iban: 'IBAN', bic: 'BIC', bankName: 'Bank',
|
||||
accountHolder: 'Kontoinhaber', isActive: 'Aktiv',
|
||||
};
|
||||
for (const [key, newVal] of Object.entries(data)) {
|
||||
if (['id', 'createdAt', 'updatedAt'].includes(key)) continue;
|
||||
const oldVal = (before as any)[key];
|
||||
const norm = (v: unknown) => (v === null || v === undefined || v === '' ? null : v);
|
||||
if (JSON.stringify(norm(oldVal)) !== JSON.stringify(norm(newVal))) {
|
||||
const label = fieldLabels[key] || key;
|
||||
const formatVal = (v: unknown) => {
|
||||
if (v === null || v === undefined || v === '') return '-';
|
||||
if (typeof v === 'boolean') return v ? 'Ja' : 'Nein';
|
||||
return String(v);
|
||||
};
|
||||
changes[label] = { von: formatVal(oldVal), nach: formatVal(newVal) };
|
||||
}
|
||||
}
|
||||
const changeList = Object.entries(changes).map(([f, c]) => `${f}: ${c.von} → ${c.nach}`).join(', ');
|
||||
await logChange({
|
||||
req, action: 'UPDATE', resourceType: 'BankCard',
|
||||
resourceId: card.id.toString(),
|
||||
label: changeList ? `Bankverbindung aktualisiert für Kunde #${customerId}: ${changeList}` : `Bankverbindung aktualisiert für Kunde #${customerId}`,
|
||||
details: Object.keys(changes).length > 0 ? changes : undefined,
|
||||
customerId,
|
||||
});
|
||||
} else {
|
||||
await logChange({
|
||||
req, action: 'UPDATE', resourceType: 'BankCard',
|
||||
resourceId: card.id.toString(),
|
||||
label: `Bankverbindung aktualisiert für Kunde #${customerId}`,
|
||||
customerId,
|
||||
});
|
||||
}
|
||||
|
||||
res.json({ success: true, data: card } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -171,7 +357,16 @@ export async function updateBankCard(req: Request, res: Response): Promise<void>
|
||||
|
||||
export async function deleteBankCard(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
await customerService.deleteBankCard(parseInt(req.params.id));
|
||||
const cardId = parseInt(req.params.id);
|
||||
const card = await prisma.bankCard.findUnique({ where: { id: cardId }, select: { customerId: true } });
|
||||
const customerId = card?.customerId;
|
||||
await customerService.deleteBankCard(cardId);
|
||||
await logChange({
|
||||
req, action: 'DELETE', resourceType: 'BankCard',
|
||||
resourceId: cardId.toString(),
|
||||
label: `Bankverbindung gelöscht für Kunde #${customerId}`,
|
||||
customerId: customerId ?? undefined,
|
||||
});
|
||||
res.json({ success: true, message: 'Bankkarte gelöscht' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -197,7 +392,14 @@ export async function getDocuments(req: Request, res: Response): Promise<void> {
|
||||
|
||||
export async function createDocument(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const doc = await customerService.createDocument(parseInt(req.params.customerId), req.body);
|
||||
const customerId = parseInt(req.params.customerId);
|
||||
const doc = await customerService.createDocument(customerId, req.body);
|
||||
await logChange({
|
||||
req, action: 'CREATE', resourceType: 'IdentityDocument',
|
||||
resourceId: doc.id.toString(),
|
||||
label: `Ausweis hinzugefügt für Kunde #${customerId}`,
|
||||
customerId,
|
||||
});
|
||||
res.status(201).json({ success: true, data: doc } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -209,7 +411,59 @@ export async function createDocument(req: Request, res: Response): Promise<void>
|
||||
|
||||
export async function updateDocument(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const doc = await customerService.updateDocument(parseInt(req.params.id), req.body);
|
||||
const docId = parseInt(req.params.id);
|
||||
const data = req.body;
|
||||
|
||||
// Vorherigen Stand laden für Audit
|
||||
const before = await prisma.identityDocument.findUnique({ where: { id: docId } });
|
||||
|
||||
const doc = await customerService.updateDocument(docId, data);
|
||||
const customerId = doc.customerId;
|
||||
|
||||
// Audit: Geänderte Felder ermitteln und loggen
|
||||
if (before) {
|
||||
const changes: Record<string, { von: unknown; nach: unknown }> = {};
|
||||
const fieldLabels: Record<string, string> = {
|
||||
type: 'Dokumenttyp', documentNumber: 'Dokumentnummer',
|
||||
issuingAuthority: 'Ausstellungsbehörde', issueDate: 'Ausstellungsdatum',
|
||||
expiryDate: 'Ablaufdatum', isActive: 'Aktiv', licenseClasses: 'Führerscheinklassen',
|
||||
};
|
||||
for (const [key, newVal] of Object.entries(data)) {
|
||||
if (['id', 'createdAt', 'updatedAt'].includes(key)) continue;
|
||||
const oldVal = (before as any)[key];
|
||||
const norm = (v: unknown) => {
|
||||
if (v === null || v === undefined || v === '') return null;
|
||||
if (v instanceof Date) return v.toISOString().split('T')[0];
|
||||
return v;
|
||||
};
|
||||
if (JSON.stringify(norm(oldVal)) !== JSON.stringify(norm(newVal))) {
|
||||
const label = fieldLabels[key] || key;
|
||||
const formatVal = (v: unknown) => {
|
||||
if (v === null || v === undefined || v === '') return '-';
|
||||
if (v instanceof Date) return v.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' });
|
||||
if (typeof v === 'boolean') return v ? 'Ja' : 'Nein';
|
||||
return String(v);
|
||||
};
|
||||
changes[label] = { von: formatVal(oldVal), nach: formatVal(newVal) };
|
||||
}
|
||||
}
|
||||
const changeList = Object.entries(changes).map(([f, c]) => `${f}: ${c.von} → ${c.nach}`).join(', ');
|
||||
await logChange({
|
||||
req, action: 'UPDATE', resourceType: 'IdentityDocument',
|
||||
resourceId: doc.id.toString(),
|
||||
label: changeList ? `Ausweis aktualisiert für Kunde #${customerId}: ${changeList}` : `Ausweis aktualisiert für Kunde #${customerId}`,
|
||||
details: Object.keys(changes).length > 0 ? changes : undefined,
|
||||
customerId,
|
||||
});
|
||||
} else {
|
||||
await logChange({
|
||||
req, action: 'UPDATE', resourceType: 'IdentityDocument',
|
||||
resourceId: doc.id.toString(),
|
||||
label: `Ausweis aktualisiert für Kunde #${customerId}`,
|
||||
customerId,
|
||||
});
|
||||
}
|
||||
|
||||
res.json({ success: true, data: doc } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -221,7 +475,16 @@ export async function updateDocument(req: Request, res: Response): Promise<void>
|
||||
|
||||
export async function deleteDocument(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
await customerService.deleteDocument(parseInt(req.params.id));
|
||||
const docId = parseInt(req.params.id);
|
||||
const doc = await prisma.identityDocument.findUnique({ where: { id: docId }, select: { customerId: true } });
|
||||
const customerId = doc?.customerId;
|
||||
await customerService.deleteDocument(docId);
|
||||
await logChange({
|
||||
req, action: 'DELETE', resourceType: 'IdentityDocument',
|
||||
resourceId: docId.toString(),
|
||||
label: `Ausweis gelöscht für Kunde #${customerId}`,
|
||||
customerId: customerId ?? undefined,
|
||||
});
|
||||
res.json({ success: true, message: 'Ausweis gelöscht' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -247,7 +510,14 @@ export async function getMeters(req: Request, res: Response): Promise<void> {
|
||||
|
||||
export async function createMeter(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const meter = await customerService.createMeter(parseInt(req.params.customerId), req.body);
|
||||
const customerId = parseInt(req.params.customerId);
|
||||
const meter = await customerService.createMeter(customerId, req.body);
|
||||
await logChange({
|
||||
req, action: 'CREATE', resourceType: 'Meter',
|
||||
resourceId: meter.id.toString(),
|
||||
label: `Zähler angelegt für Kunde #${customerId}`,
|
||||
customerId,
|
||||
});
|
||||
res.status(201).json({ success: true, data: meter } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -259,7 +529,52 @@ export async function createMeter(req: Request, res: Response): Promise<void> {
|
||||
|
||||
export async function updateMeter(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const meter = await customerService.updateMeter(parseInt(req.params.id), req.body);
|
||||
const meterId = parseInt(req.params.id);
|
||||
const data = req.body;
|
||||
|
||||
// Vorherigen Stand laden für Audit
|
||||
const before = await prisma.meter.findUnique({ where: { id: meterId } });
|
||||
|
||||
const meter = await customerService.updateMeter(meterId, data);
|
||||
const customerId = meter.customerId;
|
||||
|
||||
// Audit: Geänderte Felder ermitteln und loggen
|
||||
if (before) {
|
||||
const changes: Record<string, { von: unknown; nach: unknown }> = {};
|
||||
const fieldLabels: Record<string, string> = {
|
||||
meterNumber: 'Zählernummer', type: 'Typ', tariffModel: 'Tarifmodell',
|
||||
location: 'Standort', isActive: 'Aktiv',
|
||||
};
|
||||
for (const [key, newVal] of Object.entries(data)) {
|
||||
if (['id', 'createdAt', 'updatedAt'].includes(key)) continue;
|
||||
const oldVal = (before as any)[key];
|
||||
const norm = (v: unknown) => (v === null || v === undefined || v === '' ? null : v);
|
||||
if (JSON.stringify(norm(oldVal)) !== JSON.stringify(norm(newVal))) {
|
||||
const label = fieldLabels[key] || key;
|
||||
const formatVal = (v: unknown) => {
|
||||
if (v === null || v === undefined || v === '') return '-';
|
||||
if (typeof v === 'boolean') return v ? 'Ja' : 'Nein';
|
||||
return String(v);
|
||||
};
|
||||
changes[label] = { von: formatVal(oldVal), nach: formatVal(newVal) };
|
||||
}
|
||||
}
|
||||
const changeList = Object.entries(changes).map(([f, c]) => `${f}: ${c.von} → ${c.nach}`).join(', ');
|
||||
await logChange({
|
||||
req, action: 'UPDATE', resourceType: 'Meter',
|
||||
resourceId: meter.id.toString(),
|
||||
label: changeList ? `Zähler aktualisiert: ${changeList}` : `Zähler aktualisiert`,
|
||||
details: Object.keys(changes).length > 0 ? changes : undefined,
|
||||
customerId,
|
||||
});
|
||||
} else {
|
||||
await logChange({
|
||||
req, action: 'UPDATE', resourceType: 'Meter',
|
||||
resourceId: meter.id.toString(),
|
||||
label: `Zähler aktualisiert`,
|
||||
});
|
||||
}
|
||||
|
||||
res.json({ success: true, data: meter } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -271,7 +586,13 @@ export async function updateMeter(req: Request, res: Response): Promise<void> {
|
||||
|
||||
export async function deleteMeter(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
await customerService.deleteMeter(parseInt(req.params.id));
|
||||
const meterId = parseInt(req.params.id);
|
||||
await customerService.deleteMeter(meterId);
|
||||
await logChange({
|
||||
req, action: 'DELETE', resourceType: 'Meter',
|
||||
resourceId: meterId.toString(),
|
||||
label: `Zähler gelöscht`,
|
||||
});
|
||||
res.json({ success: true, message: 'Zähler gelöscht' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -294,13 +615,30 @@ export async function getMeterReadings(req: Request, res: Response): Promise<voi
|
||||
export async function addMeterReading(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const { readingDate, value, valueNt, unit, notes } = req.body;
|
||||
const reading = await customerService.addMeterReading(parseInt(req.params.meterId), {
|
||||
const meterId = parseInt(req.params.meterId);
|
||||
const reading = await customerService.addMeterReading(meterId, {
|
||||
readingDate: new Date(readingDate),
|
||||
value: parseFloat(value),
|
||||
valueNt: valueNt !== undefined && valueNt !== null && valueNt !== '' ? parseFloat(valueNt) : undefined,
|
||||
unit,
|
||||
notes,
|
||||
});
|
||||
|
||||
// Audit: Zählerstand mit Kontext loggen
|
||||
const meter = await prisma.meter.findUnique({
|
||||
where: { id: meterId },
|
||||
select: { meterNumber: true, customer: { select: { id: true, firstName: true, lastName: true } } },
|
||||
});
|
||||
if (meter) {
|
||||
const ntInfo = valueNt ? ` / NT: ${parseFloat(valueNt)}` : '';
|
||||
await logChange({
|
||||
req, action: 'CREATE', resourceType: 'MeterReading',
|
||||
label: `Zählerstand ${parseFloat(value)}${ntInfo} ${unit || 'kWh'} für Zähler ${meter.meterNumber} erfasst (${meter.customer.firstName} ${meter.customer.lastName})`,
|
||||
details: { zähler: meter.meterNumber, stand: parseFloat(value), datum: readingDate },
|
||||
customerId: meter.customer.id,
|
||||
});
|
||||
}
|
||||
|
||||
res.status(201).json({ success: true, data: reading } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -325,6 +663,11 @@ export async function updateMeterReading(req: Request, res: Response): Promise<v
|
||||
parseInt(req.params.readingId),
|
||||
updateData as any
|
||||
);
|
||||
await logChange({
|
||||
req, action: 'UPDATE', resourceType: 'MeterReading',
|
||||
resourceId: reading.id.toString(),
|
||||
label: `Zählerstand aktualisiert`,
|
||||
});
|
||||
res.json({ success: true, data: reading } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -336,10 +679,16 @@ export async function updateMeterReading(req: Request, res: Response): Promise<v
|
||||
|
||||
export async function deleteMeterReading(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const readingId = parseInt(req.params.readingId);
|
||||
await customerService.deleteMeterReading(
|
||||
parseInt(req.params.meterId),
|
||||
parseInt(req.params.readingId)
|
||||
readingId
|
||||
);
|
||||
await logChange({
|
||||
req, action: 'DELETE', resourceType: 'MeterReading',
|
||||
resourceId: readingId.toString(),
|
||||
label: `Zählerstand gelöscht`,
|
||||
});
|
||||
res.json({ success: true, data: null } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -389,6 +738,15 @@ export async function reportMeterReading(req: AuthRequest, res: Response): Promi
|
||||
data: { reportedBy: user.email, status: 'REPORTED' },
|
||||
});
|
||||
|
||||
// Audit
|
||||
const meterInfo = await prisma.meter.findUnique({ where: { id: meterId }, select: { meterNumber: true } });
|
||||
await logChange({
|
||||
req, action: 'CREATE', resourceType: 'MeterReading',
|
||||
label: `Zählerstand ${parsedValue} gemeldet (Zähler ${meterInfo?.meterNumber || meterId})`,
|
||||
details: { zähler: meterInfo?.meterNumber, stand: parsedValue, datum: parsedDate.toISOString() },
|
||||
customerId: user.customerId,
|
||||
});
|
||||
|
||||
res.status(201).json({ success: true, data: reading } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -437,6 +795,12 @@ export async function markReadingTransferred(req: AuthRequest, res: Response): P
|
||||
},
|
||||
});
|
||||
|
||||
await logChange({
|
||||
req, action: 'UPDATE', resourceType: 'MeterReading',
|
||||
resourceId: readingId.toString(),
|
||||
label: `Zählerstand als übertragen markiert`,
|
||||
});
|
||||
|
||||
res.json({ success: true, data: reading } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -476,11 +840,58 @@ export async function getPortalSettings(req: Request, res: Response): Promise<vo
|
||||
|
||||
export async function updatePortalSettings(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const customerId = parseInt(req.params.customerId);
|
||||
const { portalEnabled, portalEmail } = req.body;
|
||||
const settings = await customerService.updatePortalSettings(parseInt(req.params.customerId), {
|
||||
|
||||
// Vorherigen Stand laden für Audit
|
||||
const before = await prisma.customer.findUnique({
|
||||
where: { id: customerId },
|
||||
select: { portalEnabled: true, portalEmail: true },
|
||||
});
|
||||
|
||||
const settings = await customerService.updatePortalSettings(customerId, {
|
||||
portalEnabled,
|
||||
portalEmail,
|
||||
});
|
||||
|
||||
// Audit: Geänderte Felder ermitteln und loggen
|
||||
const data: Record<string, unknown> = { portalEnabled, portalEmail };
|
||||
if (before) {
|
||||
const changes: Record<string, { von: unknown; nach: unknown }> = {};
|
||||
const fieldLabels: Record<string, string> = {
|
||||
portalEnabled: 'Portal aktiv', portalEmail: 'Portal-E-Mail',
|
||||
};
|
||||
for (const [key, newVal] of Object.entries(data)) {
|
||||
if (newVal === undefined) continue;
|
||||
const oldVal = (before as any)[key];
|
||||
const norm = (v: unknown) => (v === null || v === undefined || v === '' ? null : v);
|
||||
if (JSON.stringify(norm(oldVal)) !== JSON.stringify(norm(newVal))) {
|
||||
const label = fieldLabels[key] || key;
|
||||
const formatVal = (v: unknown) => {
|
||||
if (v === null || v === undefined || v === '') return '-';
|
||||
if (typeof v === 'boolean') return v ? 'Ja' : 'Nein';
|
||||
return String(v);
|
||||
};
|
||||
changes[label] = { von: formatVal(oldVal), nach: formatVal(newVal) };
|
||||
}
|
||||
}
|
||||
const changeList = Object.entries(changes).map(([f, c]) => `${f}: ${c.von} → ${c.nach}`).join(', ');
|
||||
await logChange({
|
||||
req, action: 'UPDATE', resourceType: 'PortalSettings',
|
||||
resourceId: customerId.toString(),
|
||||
label: changeList ? `Portal-Einstellungen aktualisiert für Kunde #${customerId}: ${changeList}` : `Portal-Einstellungen aktualisiert für Kunde #${customerId}`,
|
||||
details: Object.keys(changes).length > 0 ? changes : undefined,
|
||||
customerId,
|
||||
});
|
||||
} else {
|
||||
await logChange({
|
||||
req, action: 'UPDATE', resourceType: 'PortalSettings',
|
||||
resourceId: customerId.toString(),
|
||||
label: `Portal-Einstellungen aktualisiert für Kunde #${customerId}`,
|
||||
customerId,
|
||||
});
|
||||
}
|
||||
|
||||
res.json({ success: true, data: settings } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -500,7 +911,14 @@ export async function setPortalPassword(req: Request, res: Response): Promise<vo
|
||||
} as ApiResponse);
|
||||
return;
|
||||
}
|
||||
await authService.setCustomerPortalPassword(parseInt(req.params.customerId), password);
|
||||
const customerId = parseInt(req.params.customerId);
|
||||
await authService.setCustomerPortalPassword(customerId, password);
|
||||
await logChange({
|
||||
req, action: 'UPDATE', resourceType: 'PortalSettings',
|
||||
resourceId: customerId.toString(),
|
||||
label: `Portal-Passwort gesetzt für Kunde #${customerId}`,
|
||||
customerId,
|
||||
});
|
||||
res.json({ success: true, message: 'Passwort gesetzt' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -539,12 +957,19 @@ export async function getRepresentatives(req: Request, res: Response): Promise<v
|
||||
|
||||
export async function addRepresentative(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const customerId = parseInt(req.params.customerId);
|
||||
const { representativeId, notes } = req.body;
|
||||
const representative = await customerService.addRepresentative(
|
||||
parseInt(req.params.customerId),
|
||||
customerId,
|
||||
parseInt(representativeId),
|
||||
notes
|
||||
);
|
||||
await logChange({
|
||||
req, action: 'CREATE', resourceType: 'Representative',
|
||||
resourceId: representative.id.toString(),
|
||||
label: `Vertreter hinzugefügt für Kunde #${customerId}`,
|
||||
customerId,
|
||||
});
|
||||
res.status(201).json({ success: true, data: representative } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -556,10 +981,16 @@ export async function addRepresentative(req: Request, res: Response): Promise<vo
|
||||
|
||||
export async function removeRepresentative(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const customerId = parseInt(req.params.customerId);
|
||||
await customerService.removeRepresentative(
|
||||
parseInt(req.params.customerId),
|
||||
customerId,
|
||||
parseInt(req.params.representativeId)
|
||||
);
|
||||
await logChange({
|
||||
req, action: 'DELETE', resourceType: 'Representative',
|
||||
label: `Vertreter entfernt für Kunde #${customerId}`,
|
||||
customerId,
|
||||
});
|
||||
res.json({ success: true, message: 'Vertreter entfernt' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { Request, Response } from 'express';
|
||||
import * as emailProviderService from '../services/emailProvider/emailProviderService.js';
|
||||
import { logChange } from '../services/audit.service.js';
|
||||
import { ApiResponse } from '../types/index.js';
|
||||
|
||||
// ==================== CONFIG CRUD ====================
|
||||
@@ -43,6 +44,11 @@ export async function getProviderConfig(req: Request, res: Response): Promise<vo
|
||||
export async function createProviderConfig(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const config = await emailProviderService.createProviderConfig(req.body);
|
||||
await logChange({
|
||||
req, action: 'CREATE', resourceType: 'EmailProviderConfig',
|
||||
resourceId: config.id.toString(),
|
||||
label: `E-Mail-Provider ${config.name} angelegt`,
|
||||
});
|
||||
res.status(201).json({ success: true, data: config } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -56,6 +62,11 @@ export async function updateProviderConfig(req: Request, res: Response): Promise
|
||||
try {
|
||||
const id = parseInt(req.params.id);
|
||||
const config = await emailProviderService.updateProviderConfig(id, req.body);
|
||||
await logChange({
|
||||
req, action: 'UPDATE', resourceType: 'EmailProviderConfig',
|
||||
resourceId: id.toString(),
|
||||
label: `E-Mail-Provider ${config.name} aktualisiert`,
|
||||
});
|
||||
res.json({ success: true, data: config } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -68,7 +79,13 @@ export async function updateProviderConfig(req: Request, res: Response): Promise
|
||||
export async function deleteProviderConfig(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const id = parseInt(req.params.id);
|
||||
const config = await emailProviderService.getProviderConfigById(id);
|
||||
await emailProviderService.deleteProviderConfig(id);
|
||||
await logChange({
|
||||
req, action: 'DELETE', resourceType: 'EmailProviderConfig',
|
||||
resourceId: id.toString(),
|
||||
label: `E-Mail-Provider ${config?.name || id} gelöscht`,
|
||||
});
|
||||
res.json({ success: true, message: 'Email-Provider gelöscht' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
|
||||
@@ -4,16 +4,15 @@ 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 { createAuditLog } from '../services/audit.service.js';
|
||||
import { ConsentType, DeletionRequestStatus, PrismaClient } from '@prisma/client';
|
||||
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';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
/**
|
||||
* Kundendaten exportieren (DSGVO Art. 15)
|
||||
*/
|
||||
@@ -73,6 +72,13 @@ export async function createDeletionRequest(req: AuthRequest, res: Response) {
|
||||
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);
|
||||
@@ -281,6 +287,13 @@ export async function updateCustomerConsent(req: AuthRequest, res: Response) {
|
||||
return res.status(400).json({ success: false, error: 'Ungültiger Consent-Typ' });
|
||||
}
|
||||
|
||||
const consentLabels: Record<string, string> = {
|
||||
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',
|
||||
@@ -290,6 +303,14 @@ export async function updateCustomerConsent(req: AuthRequest, res: Response) {
|
||||
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);
|
||||
@@ -350,6 +371,11 @@ export async function updatePrivacyPolicy(req: AuthRequest, res: Response) {
|
||||
|
||||
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);
|
||||
@@ -383,6 +409,11 @@ export async function updateAuthorizationTemplate(req: AuthRequest, res: Respons
|
||||
|
||||
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);
|
||||
@@ -743,6 +774,15 @@ export async function grantAuthorization(req: AuthRequest, res: Response) {
|
||||
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);
|
||||
@@ -762,6 +802,16 @@ export async function withdrawAuthorization(req: AuthRequest, res: Response) {
|
||||
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);
|
||||
@@ -791,6 +841,13 @@ export async function uploadAuthorizationDocument(req: AuthRequest, res: Respons
|
||||
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);
|
||||
@@ -810,6 +867,14 @@ export async function deleteAuthorizationDocument(req: AuthRequest, res: Respons
|
||||
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);
|
||||
@@ -852,13 +917,22 @@ export async function toggleMyAuthorization(req: AuthRequest, res: Response) {
|
||||
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 });
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Request, Response } from 'express';
|
||||
import * as invoiceService from '../services/invoice.service.js';
|
||||
import { logChange } from '../services/audit.service.js';
|
||||
import { ApiResponse } from '../types/index.js';
|
||||
|
||||
/**
|
||||
@@ -69,6 +70,12 @@ export async function addInvoice(req: Request, res: Response): Promise<void> {
|
||||
notes,
|
||||
});
|
||||
|
||||
await logChange({
|
||||
req, action: 'CREATE', resourceType: 'Invoice',
|
||||
resourceId: invoice.id.toString(),
|
||||
label: `Rechnung (${invoiceType}) hinzugefügt`,
|
||||
});
|
||||
|
||||
res.status(201).json({ success: true, data: invoice } as ApiResponse);
|
||||
} catch (error) {
|
||||
console.error('addInvoice error:', error);
|
||||
@@ -95,6 +102,12 @@ export async function updateInvoice(req: Request, res: Response): Promise<void>
|
||||
notes,
|
||||
});
|
||||
|
||||
await logChange({
|
||||
req, action: 'UPDATE', resourceType: 'Invoice',
|
||||
resourceId: invoiceId.toString(),
|
||||
label: `Rechnung aktualisiert`,
|
||||
});
|
||||
|
||||
res.json({ success: true, data: invoice } as ApiResponse);
|
||||
} catch (error) {
|
||||
console.error('updateInvoice error:', error);
|
||||
@@ -115,6 +128,12 @@ export async function deleteInvoice(req: Request, res: Response): Promise<void>
|
||||
|
||||
await invoiceService.deleteInvoice(ecdId, invoiceId);
|
||||
|
||||
await logChange({
|
||||
req, action: 'DELETE', resourceType: 'Invoice',
|
||||
resourceId: invoiceId.toString(),
|
||||
label: `Rechnung gelöscht`,
|
||||
});
|
||||
|
||||
res.json({ success: true, data: null } as ApiResponse);
|
||||
} catch (error) {
|
||||
console.error('deleteInvoice error:', error);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Request, Response } from 'express';
|
||||
import * as platformService from '../services/platform.service.js';
|
||||
import { logChange } from '../services/audit.service.js';
|
||||
import { ApiResponse } from '../types/index.js';
|
||||
|
||||
export async function getPlatforms(req: Request, res: Response): Promise<void> {
|
||||
@@ -37,6 +38,11 @@ export async function getPlatform(req: Request, res: Response): Promise<void> {
|
||||
export async function createPlatform(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const platform = await platformService.createPlatform(req.body);
|
||||
await logChange({
|
||||
req, action: 'CREATE', resourceType: 'Platform',
|
||||
resourceId: platform.id.toString(),
|
||||
label: `Vertriebsplattform ${platform.name} angelegt`,
|
||||
});
|
||||
res.status(201).json({ success: true, data: platform } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -49,6 +55,11 @@ export async function createPlatform(req: Request, res: Response): Promise<void>
|
||||
export async function updatePlatform(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const platform = await platformService.updatePlatform(parseInt(req.params.id), req.body);
|
||||
await logChange({
|
||||
req, action: 'UPDATE', resourceType: 'Platform',
|
||||
resourceId: platform.id.toString(),
|
||||
label: `Vertriebsplattform ${platform.name} aktualisiert`,
|
||||
});
|
||||
res.json({ success: true, data: platform } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -60,7 +71,14 @@ export async function updatePlatform(req: Request, res: Response): Promise<void>
|
||||
|
||||
export async function deletePlatform(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
await platformService.deletePlatform(parseInt(req.params.id));
|
||||
const platformId = parseInt(req.params.id);
|
||||
const platform = await platformService.getPlatformById(platformId);
|
||||
await platformService.deletePlatform(platformId);
|
||||
await logChange({
|
||||
req, action: 'DELETE', resourceType: 'Platform',
|
||||
resourceId: platformId.toString(),
|
||||
label: `Vertriebsplattform ${platform?.name || platformId} gelöscht`,
|
||||
});
|
||||
res.json({ success: true, message: 'Vertriebsplattform gelöscht' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Request, Response } from 'express';
|
||||
import * as providerService from '../services/provider.service.js';
|
||||
import { logChange } from '../services/audit.service.js';
|
||||
import { ApiResponse } from '../types/index.js';
|
||||
|
||||
export async function getProviders(req: Request, res: Response): Promise<void> {
|
||||
@@ -37,6 +38,11 @@ export async function getProvider(req: Request, res: Response): Promise<void> {
|
||||
export async function createProvider(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const provider = await providerService.createProvider(req.body);
|
||||
await logChange({
|
||||
req, action: 'CREATE', resourceType: 'Provider',
|
||||
resourceId: provider.id.toString(),
|
||||
label: `Anbieter ${provider.name} angelegt`,
|
||||
});
|
||||
res.status(201).json({ success: true, data: provider } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -49,6 +55,11 @@ export async function createProvider(req: Request, res: Response): Promise<void>
|
||||
export async function updateProvider(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const provider = await providerService.updateProvider(parseInt(req.params.id), req.body);
|
||||
await logChange({
|
||||
req, action: 'UPDATE', resourceType: 'Provider',
|
||||
resourceId: provider.id.toString(),
|
||||
label: `Anbieter ${provider.name} aktualisiert`,
|
||||
});
|
||||
res.json({ success: true, data: provider } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -60,7 +71,14 @@ export async function updateProvider(req: Request, res: Response): Promise<void>
|
||||
|
||||
export async function deleteProvider(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
await providerService.deleteProvider(parseInt(req.params.id));
|
||||
const providerId = parseInt(req.params.id);
|
||||
const provider = await providerService.getProviderById(providerId);
|
||||
await providerService.deleteProvider(providerId);
|
||||
await logChange({
|
||||
req, action: 'DELETE', resourceType: 'Provider',
|
||||
resourceId: providerId.toString(),
|
||||
label: `Anbieter ${provider?.name || providerId} gelöscht`,
|
||||
});
|
||||
res.json({ success: true, message: 'Anbieter gelöscht' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Request, Response } from 'express';
|
||||
import * as stressfreiEmailService from '../services/stressfreiEmail.service.js';
|
||||
import { logChange } from '../services/audit.service.js';
|
||||
import { ApiResponse } from '../types/index.js';
|
||||
|
||||
export async function getEmailsByCustomer(req: Request, res: Response): Promise<void> {
|
||||
@@ -42,6 +43,12 @@ export async function createEmail(req: Request, res: Response): Promise<void> {
|
||||
...req.body,
|
||||
customerId,
|
||||
});
|
||||
await logChange({
|
||||
req, action: 'CREATE', resourceType: 'StressfreiEmail',
|
||||
resourceId: email.id.toString(),
|
||||
label: `Stressfrei-Wechseln Adresse angelegt für Kunde #${customerId}`,
|
||||
customerId,
|
||||
});
|
||||
res.status(201).json({ success: true, data: email } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -54,6 +61,11 @@ export async function createEmail(req: Request, res: Response): Promise<void> {
|
||||
export async function updateEmail(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const email = await stressfreiEmailService.updateEmail(parseInt(req.params.id), req.body);
|
||||
await logChange({
|
||||
req, action: 'UPDATE', resourceType: 'StressfreiEmail',
|
||||
resourceId: email.id.toString(),
|
||||
label: `Stressfrei-Wechseln Adresse aktualisiert`,
|
||||
});
|
||||
res.json({ success: true, data: email } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -65,7 +77,13 @@ export async function updateEmail(req: Request, res: Response): Promise<void> {
|
||||
|
||||
export async function deleteEmail(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
await stressfreiEmailService.deleteEmail(parseInt(req.params.id));
|
||||
const emailId = parseInt(req.params.id);
|
||||
await stressfreiEmailService.deleteEmail(emailId);
|
||||
await logChange({
|
||||
req, action: 'DELETE', resourceType: 'StressfreiEmail',
|
||||
resourceId: emailId.toString(),
|
||||
label: `Stressfrei-Wechseln Adresse gelöscht`,
|
||||
});
|
||||
res.json({ success: true, message: 'Stressfrei-Wechseln Adresse gelöscht' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Request, Response } from 'express';
|
||||
import * as tariffService from '../services/tariff.service.js';
|
||||
import { logChange } from '../services/audit.service.js';
|
||||
import { ApiResponse } from '../types/index.js';
|
||||
|
||||
export async function getTariffs(req: Request, res: Response): Promise<void> {
|
||||
@@ -39,6 +40,11 @@ export async function createTariff(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const providerId = parseInt(req.params.providerId);
|
||||
const tariff = await tariffService.createTariff({ ...req.body, providerId });
|
||||
await logChange({
|
||||
req, action: 'CREATE', resourceType: 'Tariff',
|
||||
resourceId: tariff.id.toString(),
|
||||
label: `Tarif ${tariff.name} angelegt`,
|
||||
});
|
||||
res.status(201).json({ success: true, data: tariff } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -51,6 +57,11 @@ export async function createTariff(req: Request, res: Response): Promise<void> {
|
||||
export async function updateTariff(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const tariff = await tariffService.updateTariff(parseInt(req.params.id), req.body);
|
||||
await logChange({
|
||||
req, action: 'UPDATE', resourceType: 'Tariff',
|
||||
resourceId: tariff.id.toString(),
|
||||
label: `Tarif ${tariff.name} aktualisiert`,
|
||||
});
|
||||
res.json({ success: true, data: tariff } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -62,7 +73,14 @@ export async function updateTariff(req: Request, res: Response): Promise<void> {
|
||||
|
||||
export async function deleteTariff(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
await tariffService.deleteTariff(parseInt(req.params.id));
|
||||
const tariffId = parseInt(req.params.id);
|
||||
const tariff = await tariffService.getTariffById(tariffId);
|
||||
await tariffService.deleteTariff(tariffId);
|
||||
await logChange({
|
||||
req, action: 'DELETE', resourceType: 'Tariff',
|
||||
resourceId: tariffId.toString(),
|
||||
label: `Tarif ${tariff?.name || tariffId} gelöscht`,
|
||||
});
|
||||
res.json({ success: true, message: 'Tarif gelöscht' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Request, Response } from 'express';
|
||||
import prisma from '../lib/prisma.js';
|
||||
import * as userService from '../services/user.service.js';
|
||||
import { logChange } from '../services/audit.service.js';
|
||||
import { ApiResponse } from '../types/index.js';
|
||||
|
||||
// Users
|
||||
@@ -48,6 +50,11 @@ export async function getUser(req: Request, res: Response): Promise<void> {
|
||||
export async function createUser(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const user = await userService.createUser(req.body);
|
||||
await logChange({
|
||||
req, action: 'CREATE', resourceType: 'User',
|
||||
resourceId: user.id.toString(),
|
||||
label: `Benutzer ${user.firstName} ${user.lastName} angelegt`,
|
||||
});
|
||||
res.status(201).json({ success: true, data: user } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -59,7 +66,49 @@ export async function createUser(req: Request, res: Response): Promise<void> {
|
||||
|
||||
export async function updateUser(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const user = await userService.updateUser(parseInt(req.params.id), req.body);
|
||||
const userId = parseInt(req.params.id);
|
||||
const data = req.body;
|
||||
|
||||
// Vorherigen Stand laden für Audit
|
||||
const before = await prisma.user.findUnique({ where: { id: userId } });
|
||||
|
||||
const user = await userService.updateUser(userId, data);
|
||||
if (user) {
|
||||
// Audit: Geänderte Felder ermitteln und loggen
|
||||
if (before) {
|
||||
const changes: Record<string, { von: unknown; nach: unknown }> = {};
|
||||
const fieldLabels: Record<string, string> = {
|
||||
email: 'E-Mail', firstName: 'Vorname', lastName: 'Nachname', isActive: 'Aktiv',
|
||||
};
|
||||
for (const [key, newVal] of Object.entries(data)) {
|
||||
if (['id', 'createdAt', 'updatedAt'].includes(key)) continue;
|
||||
const oldVal = (before as any)[key];
|
||||
const norm = (v: unknown) => (v === null || v === undefined || v === '' ? null : v);
|
||||
if (JSON.stringify(norm(oldVal)) !== JSON.stringify(norm(newVal))) {
|
||||
const label = fieldLabels[key] || key;
|
||||
const formatVal = (v: unknown) => {
|
||||
if (v === null || v === undefined || v === '') return '-';
|
||||
if (typeof v === 'boolean') return v ? 'Ja' : 'Nein';
|
||||
return String(v);
|
||||
};
|
||||
changes[label] = { von: formatVal(oldVal), nach: formatVal(newVal) };
|
||||
}
|
||||
}
|
||||
const changeList = Object.entries(changes).map(([f, c]) => `${f}: ${c.von} → ${c.nach}`).join(', ');
|
||||
await logChange({
|
||||
req, action: 'UPDATE', resourceType: 'User',
|
||||
resourceId: user.id.toString(),
|
||||
label: changeList ? `Benutzer ${user.firstName} ${user.lastName} aktualisiert: ${changeList}` : `Benutzer ${user.firstName} ${user.lastName} aktualisiert`,
|
||||
details: Object.keys(changes).length > 0 ? changes : undefined,
|
||||
});
|
||||
} else {
|
||||
await logChange({
|
||||
req, action: 'UPDATE', resourceType: 'User',
|
||||
resourceId: user.id.toString(),
|
||||
label: `Benutzer ${user.firstName} ${user.lastName} aktualisiert`,
|
||||
});
|
||||
}
|
||||
}
|
||||
res.json({ success: true, data: user } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -71,7 +120,14 @@ export async function updateUser(req: Request, res: Response): Promise<void> {
|
||||
|
||||
export async function deleteUser(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
await userService.deleteUser(parseInt(req.params.id));
|
||||
const userId = parseInt(req.params.id);
|
||||
const userBefore = await userService.getUserById(userId);
|
||||
await userService.deleteUser(userId);
|
||||
await logChange({
|
||||
req, action: 'DELETE', resourceType: 'User',
|
||||
resourceId: userId.toString(),
|
||||
label: `Benutzer ${userBefore?.firstName || ''} ${userBefore?.lastName || ''} gelöscht`,
|
||||
});
|
||||
res.json({ success: true, message: 'Benutzer gelöscht' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -116,6 +172,11 @@ export async function getRole(req: Request, res: Response): Promise<void> {
|
||||
export async function createRole(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const role = await userService.createRole(req.body);
|
||||
await logChange({
|
||||
req, action: 'CREATE', resourceType: 'Role',
|
||||
resourceId: role.id.toString(),
|
||||
label: `Rolle ${role.name} angelegt`,
|
||||
});
|
||||
res.status(201).json({ success: true, data: role } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -128,6 +189,13 @@ export async function createRole(req: Request, res: Response): Promise<void> {
|
||||
export async function updateRole(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const role = await userService.updateRole(parseInt(req.params.id), req.body);
|
||||
if (role) {
|
||||
await logChange({
|
||||
req, action: 'UPDATE', resourceType: 'Role',
|
||||
resourceId: role.id.toString(),
|
||||
label: `Rolle ${role.name} aktualisiert`,
|
||||
});
|
||||
}
|
||||
res.json({ success: true, data: role } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
@@ -139,7 +207,14 @@ export async function updateRole(req: Request, res: Response): Promise<void> {
|
||||
|
||||
export async function deleteRole(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
await userService.deleteRole(parseInt(req.params.id));
|
||||
const roleId = parseInt(req.params.id);
|
||||
const role = await userService.getRoleById(roleId);
|
||||
await userService.deleteRole(roleId);
|
||||
await logChange({
|
||||
req, action: 'DELETE', resourceType: 'Role',
|
||||
resourceId: roleId.toString(),
|
||||
label: `Rolle ${role?.name || roleId} gelöscht`,
|
||||
});
|
||||
res.json({ success: true, message: 'Rolle gelöscht' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
|
||||
@@ -17,6 +17,11 @@ const AUDITED_MODELS = [
|
||||
'ContractCategory',
|
||||
'AppSetting',
|
||||
'CustomerConsent',
|
||||
'EnergyContractDetails',
|
||||
'RepresentativeAuthorization',
|
||||
'ContractMeter',
|
||||
'EmailProviderConfig',
|
||||
'ContractTask',
|
||||
];
|
||||
|
||||
// Sensible Felder die aus dem Audit-Log gefiltert werden
|
||||
|
||||
@@ -387,17 +387,44 @@ export function auditMiddleware(req: AuthRequest, res: Response, next: NextFunct
|
||||
};
|
||||
|
||||
// Response-Ende abfangen für Logging
|
||||
// Audit-Kontext hier erfassen (bevor AsyncLocalStorage den Kontext verliert)
|
||||
let capturedAuditContext: ReturnType<typeof getAuditContext> | undefined;
|
||||
|
||||
const origEnd = res.end;
|
||||
(res as any).end = function(chunk?: any, encoding?: any, cb?: any) {
|
||||
// Kontext VOR dem Ende erfassen
|
||||
capturedAuditContext = getAuditContext();
|
||||
return origEnd.call(this, chunk, encoding, cb);
|
||||
};
|
||||
|
||||
res.on('finish', () => {
|
||||
// Async Logging - blockiert nicht die Response
|
||||
setImmediate(async () => {
|
||||
try {
|
||||
const durationMs = Date.now() - startTime;
|
||||
const action = determineAction(req.method, req.path, responseSuccess);
|
||||
|
||||
// READ-Aktionen nicht loggen (nur Änderungen, Logins und Exporte)
|
||||
if (action === 'READ') return;
|
||||
|
||||
// Routen die bereits gezielt via logChange() geloggt werden → nicht doppelt loggen
|
||||
const manuallyLoggedPaths = [
|
||||
'/api/customers',
|
||||
'/api/contracts',
|
||||
'/api/meters',
|
||||
'/api/gdpr',
|
||||
'/api/upload',
|
||||
];
|
||||
// Login/Logout immer loggen
|
||||
if (action !== 'LOGIN' && action !== 'LOGOUT' && action !== 'LOGIN_FAILED') {
|
||||
if (manuallyLoggedPaths.some(p => req.originalUrl?.startsWith(p) || req.baseUrl?.startsWith(p))) return;
|
||||
}
|
||||
|
||||
const resourceId = mapping.extractId?.(req);
|
||||
const dataSubjectId = extractDataSubjectId(req);
|
||||
|
||||
// Audit-Kontext abrufen (enthält Before/After-Werte von Prisma Middleware)
|
||||
const auditContext = getAuditContext();
|
||||
// Audit-Kontext nutzen (wurde vor Response-Ende erfasst)
|
||||
const auditContext = capturedAuditContext;
|
||||
|
||||
// Menschenlesbares Label generieren
|
||||
const resourceLabel = generateHumanLabel(action, mapping.type, req, responseBody);
|
||||
@@ -405,7 +432,7 @@ export function auditMiddleware(req: AuthRequest, res: Response, next: NextFunct
|
||||
await createAuditLog({
|
||||
userId: req.user?.userId,
|
||||
userEmail: req.user?.email || 'anonymous',
|
||||
userRole: req.user?.permissions?.join(', '),
|
||||
userRole: req.user?.isCustomerPortal ? 'Kundenportal' : (req.user as any)?.roleName || 'Mitarbeiter',
|
||||
customerId: req.user?.customerId,
|
||||
isCustomerPortal: req.user?.isCustomerPortal,
|
||||
action,
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { Response, NextFunction } from 'express';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import prisma from '../lib/prisma.js';
|
||||
import { AuthRequest, JwtPayload } from '../types/index.js';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
export async function authenticate(
|
||||
req: AuthRequest,
|
||||
res: Response,
|
||||
|
||||
@@ -10,17 +10,20 @@ router.use(authenticate);
|
||||
// Audit-Logs abrufen
|
||||
router.get('/', requirePermission('audit:read'), auditLogController.getAuditLogs);
|
||||
|
||||
// Einzelnes Audit-Log abrufen
|
||||
router.get('/:id', requirePermission('audit:read'), auditLogController.getAuditLogById);
|
||||
// Audit-Logs exportieren (muss VOR /:id stehen!)
|
||||
router.get('/export', requirePermission('audit:read'), auditLogController.exportAuditLogs);
|
||||
|
||||
// Audit-Logs für einen Kunden (DSGVO)
|
||||
router.get('/customer/:customerId', requirePermission('audit:read'), auditLogController.getAuditLogsByCustomer);
|
||||
|
||||
// Audit-Logs exportieren
|
||||
router.get('/export', requirePermission('audit:export'), auditLogController.exportAuditLogs);
|
||||
// Einzelnes Audit-Log abrufen
|
||||
router.get('/:id', requirePermission('audit:read'), auditLogController.getAuditLogById);
|
||||
|
||||
// Hash-Ketten-Integrität prüfen
|
||||
router.post('/verify', requirePermission('audit:admin'), auditLogController.verifyIntegrity);
|
||||
router.post('/verify', requirePermission('audit:read'), auditLogController.verifyIntegrity);
|
||||
|
||||
// Hash-Kette reparieren
|
||||
router.post('/rehash', requirePermission('audit:admin'), auditLogController.rehashAll);
|
||||
|
||||
// Retention-Policies
|
||||
router.get('/retention-policies', requirePermission('audit:admin'), auditLogController.getRetentionPolicies);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Router, Response } from 'express';
|
||||
import { PrismaClient, Prisma } from '@prisma/client';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import prisma from '../lib/prisma.js';
|
||||
import { authenticate, requirePermission } from '../middleware/auth.js';
|
||||
import { AuthRequest } from '../types/index.js';
|
||||
|
||||
const router = Router();
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// Setup-Endpunkt: Erstellt die developer:access Permission und fügt sie der Admin-Rolle hinzu
|
||||
// Dieser Endpunkt erfordert keine Authentifizierung, da er nur einmalig zum Setup verwendet wird
|
||||
|
||||
@@ -2,12 +2,12 @@ import { Router, Response } from 'express';
|
||||
import multer from 'multer';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import prisma from '../lib/prisma.js';
|
||||
import { authenticate, requirePermission } from '../middleware/auth.js';
|
||||
import { AuthRequest } from '../types/index.js';
|
||||
import { logChange } from '../services/audit.service.js';
|
||||
|
||||
const router = Router();
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// Uploads-Verzeichnis erstellen falls nicht vorhanden
|
||||
const uploadsDir = path.join(process.cwd(), 'uploads');
|
||||
@@ -450,6 +450,15 @@ router.post(
|
||||
});
|
||||
}
|
||||
|
||||
// Audit
|
||||
const cust = await prisma.customer.findUnique({ where: { id: customerId }, select: { firstName: true, lastName: true } });
|
||||
await logChange({
|
||||
req, action: 'CREATE', resourceType: 'CustomerConsent',
|
||||
label: `Datenschutzerklärung-PDF hochgeladen für ${cust?.firstName} ${cust?.lastName} – alle Einwilligungen erteilt`,
|
||||
details: { aktion: 'PDF hochgeladen', einwilligungen: 'alle erteilt', quelle: 'papier' },
|
||||
customerId,
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
@@ -504,6 +513,15 @@ router.delete(
|
||||
data: { status: 'WITHDRAWN', withdrawnAt: new Date() },
|
||||
});
|
||||
|
||||
// Audit
|
||||
const cust = await prisma.customer.findUnique({ where: { id: customerId }, select: { firstName: true, lastName: true } });
|
||||
await logChange({
|
||||
req, action: 'DELETE', resourceType: 'CustomerConsent',
|
||||
label: `Datenschutzerklärung-PDF gelöscht für ${cust?.firstName} ${cust?.lastName} – Papier-Einwilligungen widerrufen`,
|
||||
details: { aktion: 'PDF gelöscht', einwilligungen: 'papier-basierte widerrufen' },
|
||||
customerId,
|
||||
});
|
||||
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Delete error:', error);
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
import prisma from '../lib/prisma.js';
|
||||
|
||||
// Default settings
|
||||
const DEFAULT_SETTINGS: Record<string, string> = {
|
||||
|
||||
@@ -3,6 +3,42 @@ import crypto from 'crypto';
|
||||
import { encrypt, decrypt } from '../utils/encryption.js';
|
||||
import prisma from '../lib/prisma.js';
|
||||
|
||||
/**
|
||||
* Vereinfachte Audit-Log-Funktion für gezielte Änderungsprotokolle.
|
||||
* Wird direkt in Controllern aufgerufen mit aussagekräftigen Details.
|
||||
*/
|
||||
export async function logChange(opts: {
|
||||
req: any; // Express Request (für userId, email, IP)
|
||||
action: AuditAction;
|
||||
resourceType: string;
|
||||
resourceId?: string;
|
||||
label: string; // Menschenlesbares Label z.B. "Vollmacht für Stefan Hacker widerrufen"
|
||||
details?: Record<string, unknown>; // Zusätzliche Details z.B. { vorher: 'erteilt', nachher: 'widerrufen' }
|
||||
customerId?: number;
|
||||
}) {
|
||||
try {
|
||||
const user = opts.req?.user;
|
||||
await createAuditLog({
|
||||
userId: user?.userId,
|
||||
userEmail: user?.email || 'system',
|
||||
userRole: user?.isCustomerPortal ? 'Kundenportal' : 'Mitarbeiter',
|
||||
customerId: user?.customerId,
|
||||
isCustomerPortal: user?.isCustomerPortal,
|
||||
action: opts.action,
|
||||
resourceType: opts.resourceType,
|
||||
resourceId: opts.resourceId,
|
||||
resourceLabel: opts.label,
|
||||
endpoint: opts.req?.path || '',
|
||||
httpMethod: opts.req?.method || '',
|
||||
ipAddress: opts.req?.socket?.remoteAddress || opts.req?.headers?.['x-forwarded-for'] || 'unknown',
|
||||
dataSubjectId: opts.customerId,
|
||||
changesAfter: opts.details,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[logChange] Fehler:', error);
|
||||
}
|
||||
}
|
||||
|
||||
export interface CreateAuditLogData {
|
||||
userId?: number;
|
||||
userEmail: string;
|
||||
@@ -101,16 +137,11 @@ function determineSensitivity(resourceType: string): AuditSensitivity {
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft ob Änderungen verschlüsselt werden sollen
|
||||
* Prüft ob Änderungen verschlüsselt werden sollen.
|
||||
* Deaktiviert - sensible Felder werden bereits von der Prisma-Middleware als [REDACTED] gefiltert.
|
||||
*/
|
||||
function shouldEncryptChanges(resourceType: string): boolean {
|
||||
const encryptedTypes = [
|
||||
'BankCard',
|
||||
'IdentityDocument',
|
||||
'User',
|
||||
'Customer', // Enthält Portal-Passwörter
|
||||
];
|
||||
return encryptedTypes.includes(resourceType);
|
||||
function shouldEncryptChanges(_resourceType: string): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -381,6 +412,49 @@ export async function verifyIntegrity(fromId?: number, toId?: number): Promise<{
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash-Kette komplett neu berechnen (Reparatur)
|
||||
*/
|
||||
export async function rehashAll(): Promise<{ rehashedCount: number }> {
|
||||
const logs = await prisma.auditLog.findMany({
|
||||
orderBy: { id: 'asc' },
|
||||
select: {
|
||||
id: true,
|
||||
userEmail: true,
|
||||
action: true,
|
||||
resourceType: true,
|
||||
resourceId: true,
|
||||
endpoint: true,
|
||||
createdAt: true,
|
||||
},
|
||||
});
|
||||
|
||||
let previousHash: string | null = null;
|
||||
let count = 0;
|
||||
|
||||
for (const log of logs) {
|
||||
const hash = generateHash({
|
||||
userEmail: log.userEmail,
|
||||
action: log.action,
|
||||
resourceType: log.resourceType,
|
||||
resourceId: log.resourceId,
|
||||
endpoint: log.endpoint,
|
||||
createdAt: log.createdAt,
|
||||
previousHash,
|
||||
});
|
||||
|
||||
await prisma.auditLog.update({
|
||||
where: { id: log.id },
|
||||
data: { hash, previousHash },
|
||||
});
|
||||
|
||||
previousHash = hash;
|
||||
count++;
|
||||
}
|
||||
|
||||
return { rehashedCount: count };
|
||||
}
|
||||
|
||||
/**
|
||||
* Exportiert Audit-Logs als JSON oder CSV
|
||||
*/
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import prisma from '../lib/prisma.js';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { JwtPayload } from '../types/index.js';
|
||||
import { encrypt, decrypt } from '../utils/encryption.js';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// Mitarbeiter-Login
|
||||
export async function login(email: string, password: string) {
|
||||
const user = await prisma.user.findUnique({
|
||||
|
||||
@@ -4,15 +4,13 @@
|
||||
* Ermöglicht Backup und Restore der Datenbank und Uploads über die Web-Oberfläche.
|
||||
*/
|
||||
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import prisma from '../lib/prisma.js';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import archiver from 'archiver';
|
||||
import AdmZip from 'adm-zip';
|
||||
import bcrypt from 'bcryptjs';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// Verzeichnisse
|
||||
const BACKUPS_DIR = path.join(__dirname, '../../prisma/backups');
|
||||
const UPLOADS_DIR = path.join(process.cwd(), 'uploads');
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
// ==================== CACHED EMAIL SERVICE ====================
|
||||
// Service für E-Mail-Caching und Vertragszuordnung
|
||||
|
||||
import { PrismaClient, CachedEmail, Prisma, EmailFolder } from '@prisma/client';
|
||||
import { CachedEmail, Prisma, EmailFolder } from '@prisma/client';
|
||||
import prisma from '../lib/prisma.js';
|
||||
import { decrypt } from '../utils/encryption.js';
|
||||
import { fetchEmails, ImapCredentials, FetchedEmail, moveToTrash, restoreFromTrash, permanentDelete } from './imapService.js';
|
||||
import { getImapSmtpSettings } from './emailProvider/emailProviderService.js';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// ==================== TYPES ====================
|
||||
|
||||
export interface CachedEmailWithRelations extends CachedEmail {
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
import prisma from '../lib/prisma.js';
|
||||
|
||||
export async function getAllCancellationPeriods(includeInactive = false) {
|
||||
const where = includeInactive ? {} : { isActive: true };
|
||||
|
||||
@@ -253,8 +253,8 @@ export const CONSENT_TYPE_LABELS: Record<ConsentType, { label: string; descripti
|
||||
description: 'Grundlegende Verarbeitung personenbezogener Daten zur Vertragserfüllung',
|
||||
},
|
||||
MARKETING_EMAIL: {
|
||||
label: 'E-Mail-Marketing',
|
||||
description: 'Zusendung von Werbung und Angeboten per E-Mail',
|
||||
label: 'Elektronisches Marketing',
|
||||
description: 'Zusendung von Werbung und Angeboten über elektronische Kommunikationswege (E-Mail, Messenger etc.)',
|
||||
},
|
||||
MARKETING_PHONE: {
|
||||
label: 'Telefonmarketing',
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
import prisma from '../lib/prisma.js';
|
||||
|
||||
export async function getAllContractDurations(includeInactive = false) {
|
||||
const where = includeInactive ? {} : { isActive: true };
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { PrismaClient, ContractType, ContractStatus } from '@prisma/client';
|
||||
import { ContractType, ContractStatus } from '@prisma/client';
|
||||
import prisma from '../lib/prisma.js';
|
||||
import { generateContractNumber, paginate, buildPaginationResponse } from '../utils/helpers.js';
|
||||
import { encrypt, decrypt } from '../utils/encryption.js';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
export interface ContractFilters {
|
||||
customerId?: number;
|
||||
customerIds?: number[]; // Für Kundenportal: eigene ID + vertretene Kunden
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
import prisma from '../lib/prisma.js';
|
||||
|
||||
export async function getAllContractCategories(includeInactive = false) {
|
||||
return prisma.contractCategory.findMany({
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { PrismaClient, ContractStatus
|
||||
} from '@prisma/client';
|
||||
import { ContractStatus } from '@prisma/client';
|
||||
import prisma from '../lib/prisma.js';
|
||||
import * as appSettingService from './appSetting.service.js';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// Typen für das Cockpit
|
||||
export type UrgencyLevel = 'critical' | 'warning' | 'ok' | 'none';
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
import prisma from '../lib/prisma.js';
|
||||
|
||||
export interface CreateHistoryEntryData {
|
||||
title: string;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { PrismaClient, ContractTaskStatus } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
import { ContractTaskStatus } from '@prisma/client';
|
||||
import prisma from '../lib/prisma.js';
|
||||
|
||||
export interface ContractTaskFilters {
|
||||
contractId: number;
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { PrismaClient, CustomerType, ContractStatus } from '@prisma/client';
|
||||
import { CustomerType, ContractStatus } from '@prisma/client';
|
||||
import prisma from '../lib/prisma.js';
|
||||
import { generateCustomerNumber, paginate, buildPaginationResponse } from '../utils/helpers.js';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// Helper zum Löschen von Dateien
|
||||
function deleteFileIfExists(filePath: string | null) {
|
||||
if (!filePath) return;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// ==================== EMAIL PROVIDER SERVICE ====================
|
||||
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import prisma from '../../lib/prisma.js';
|
||||
import { decrypt } from '../../utils/encryption.js';
|
||||
import {
|
||||
IEmailProvider,
|
||||
@@ -12,8 +12,6 @@ import {
|
||||
} from './types.js';
|
||||
import { PleskEmailProvider } from './pleskProvider.js';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// Factory-Funktion um den richtigen Provider zu erstellen
|
||||
function createProvider(config: EmailProviderConfig): IEmailProvider {
|
||||
switch (config.type) {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { PrismaClient, InvoiceType } from '@prisma/client';
|
||||
import { InvoiceType } from '@prisma/client';
|
||||
import prisma from '../lib/prisma.js';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
export interface CreateInvoiceData {
|
||||
invoiceDate: Date;
|
||||
invoiceType: InvoiceType;
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
import prisma from '../lib/prisma.js';
|
||||
|
||||
export async function getAllPlatforms(includeInactive = false) {
|
||||
const where = includeInactive ? {} : { isActive: true };
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
import prisma from '../lib/prisma.js';
|
||||
|
||||
export async function getAllProviders(includeInactive = false) {
|
||||
const where = includeInactive ? {} : { isActive: true };
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import prisma from '../lib/prisma.js';
|
||||
import { encrypt, decrypt } from '../utils/encryption.js';
|
||||
import {
|
||||
provisionEmail,
|
||||
@@ -10,8 +10,6 @@ import {
|
||||
} from './emailProvider/emailProviderService.js';
|
||||
import { generateSecurePassword } from '../utils/passwordGenerator.js';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
export async function getEmailsByCustomerId(customerId: number, includeInactive = false) {
|
||||
const where: Record<string, unknown> = { customerId };
|
||||
if (!includeInactive) {
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
import prisma from '../lib/prisma.js';
|
||||
|
||||
export async function getTariffsByProvider(providerId: number, includeInactive = false) {
|
||||
const where: { providerId: number; isActive?: boolean } = { providerId };
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import prisma from '../lib/prisma.js';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import { paginate, buildPaginationResponse } from '../utils/helpers.js';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
export interface UserFilters {
|
||||
search?: string;
|
||||
isActive?: boolean;
|
||||
|
||||
Reference in New Issue
Block a user