gdpr audit implemented, email log, vollmachten, pdf delete cancel data privacy and vollmachten, removed message no id card in engergy car, and other contracts that are not telecom contracts, added insert counter for engery
This commit is contained in:
+195
-3
@@ -105,6 +105,8 @@ async function getCockpitData() {
|
||||
const criticalDays = parseInt(settings.deadlineCriticalDays) || 14;
|
||||
const warningDays = parseInt(settings.deadlineWarningDays) || 42;
|
||||
const okDays = parseInt(settings.deadlineOkDays) || 90;
|
||||
const docExpiryCriticalDays = parseInt(settings.documentExpiryCriticalDays) || 30;
|
||||
const docExpiryWarningDays = parseInt(settings.documentExpiryWarningDays) || 90;
|
||||
// Lade alle relevanten Verträge (inkl. CANCELLED/DEACTIVATED für Schlussrechnung-Check)
|
||||
const contracts = await prisma.contract.findMany({
|
||||
where: {
|
||||
@@ -191,8 +193,36 @@ async function getCockpitData() {
|
||||
openTasks: 0,
|
||||
pendingContracts: 0,
|
||||
reviewDue: 0,
|
||||
missingConsents: 0,
|
||||
},
|
||||
};
|
||||
// Consent-Daten batch-laden für alle Kunden
|
||||
const allConsents = await prisma.customerConsent.findMany({
|
||||
where: { status: 'GRANTED' },
|
||||
select: { customerId: true, consentType: true },
|
||||
});
|
||||
// Map: customerId → Set<consentType>
|
||||
const grantedConsentsMap = new Map();
|
||||
for (const c of allConsents) {
|
||||
if (!grantedConsentsMap.has(c.customerId)) {
|
||||
grantedConsentsMap.set(c.customerId, new Set());
|
||||
}
|
||||
grantedConsentsMap.get(c.customerId).add(c.consentType);
|
||||
}
|
||||
// Widerrufene Consents laden
|
||||
const withdrawnConsents = await prisma.customerConsent.findMany({
|
||||
where: { status: 'WITHDRAWN' },
|
||||
select: { customerId: true, consentType: true },
|
||||
});
|
||||
const withdrawnConsentsMap = new Map();
|
||||
for (const c of withdrawnConsents) {
|
||||
if (!withdrawnConsentsMap.has(c.customerId)) {
|
||||
withdrawnConsentsMap.set(c.customerId, new Set());
|
||||
}
|
||||
withdrawnConsentsMap.get(c.customerId).add(c.consentType);
|
||||
}
|
||||
// Track welche Kunden bereits eine Consent-Warnung bekommen haben (nur einmal pro Kunde)
|
||||
const customerConsentWarned = new Set();
|
||||
for (const contract of contracts) {
|
||||
const issues = [];
|
||||
// SNOOZE-LOGIK: Prüfen ob Snooze aktiv ist (für Fristen-Unterdrückung)
|
||||
@@ -349,16 +379,41 @@ async function getCockpitData() {
|
||||
});
|
||||
summary.byCategory.missingData++;
|
||||
}
|
||||
// 7b. KEIN AUSWEIS (für DSL, FIBER, CABLE, MOBILE ist dies ein kritisches Problem)
|
||||
if (!contract.identityDocumentId) {
|
||||
// 7b. KEIN AUSWEIS (nur für Telekommunikationsprodukte relevant)
|
||||
const requiresIdentityDocument = ['DSL', 'FIBER', 'CABLE', 'MOBILE'].includes(contract.type);
|
||||
if (requiresIdentityDocument && !contract.identityDocumentId) {
|
||||
issues.push({
|
||||
type: 'missing_identity_document',
|
||||
label: 'Ausweis fehlt',
|
||||
urgency: requiresBankAndId ? 'critical' : 'warning',
|
||||
urgency: 'critical',
|
||||
details: 'Kein Ausweisdokument verknüpft',
|
||||
});
|
||||
summary.byCategory.missingData++;
|
||||
}
|
||||
// 7c. AUSWEIS LÄUFT AB (nur aktive Ausweise prüfen)
|
||||
if (contract.identityDocument && contract.identityDocument.isActive && contract.identityDocument.expiryDate) {
|
||||
const expiryDate = new Date(contract.identityDocument.expiryDate);
|
||||
const today = new Date();
|
||||
const daysUntilExpiry = Math.ceil((expiryDate.getTime() - today.getTime()) / (1000 * 60 * 60 * 24));
|
||||
if (daysUntilExpiry < 0) {
|
||||
issues.push({
|
||||
type: 'identity_document_expired',
|
||||
label: 'Ausweis abgelaufen',
|
||||
urgency: 'critical',
|
||||
details: `Ausweis seit ${Math.abs(daysUntilExpiry)} Tagen abgelaufen (${expiryDate.toLocaleDateString('de-DE')})`,
|
||||
});
|
||||
summary.byCategory.missingData++;
|
||||
}
|
||||
else if (daysUntilExpiry <= docExpiryWarningDays) {
|
||||
issues.push({
|
||||
type: 'identity_document_expiring',
|
||||
label: 'Ausweis läuft ab',
|
||||
urgency: daysUntilExpiry <= docExpiryCriticalDays ? 'critical' : 'warning',
|
||||
details: `Ausweis läuft in ${daysUntilExpiry} Tagen ab (${expiryDate.toLocaleDateString('de-DE')})`,
|
||||
});
|
||||
summary.byCategory.cancellationDeadlines++;
|
||||
}
|
||||
}
|
||||
// 8. ENERGIE-SPEZIFISCH: KEIN ZÄHLER
|
||||
if (['ELECTRICITY', 'GAS'].includes(contract.type) && contract.energyDetails) {
|
||||
if (!contract.energyDetails.meterId) {
|
||||
@@ -475,6 +530,35 @@ async function getCockpitData() {
|
||||
}
|
||||
}
|
||||
}
|
||||
// #14 - Consent-Prüfung (nur für aktive Verträge, einmal pro Kunde)
|
||||
if (['ACTIVE', 'PENDING', 'DRAFT'].includes(contract.status) && !customerConsentWarned.has(contract.customer.id)) {
|
||||
const granted = grantedConsentsMap.get(contract.customer.id);
|
||||
const withdrawn = withdrawnConsentsMap.get(contract.customer.id);
|
||||
const requiredTypes = ['DATA_PROCESSING', 'MARKETING_EMAIL', 'MARKETING_PHONE', 'DATA_SHARING_PARTNER'];
|
||||
if (withdrawn && withdrawn.size > 0) {
|
||||
// Mindestens eine Einwilligung widerrufen
|
||||
issues.push({
|
||||
type: 'consent_withdrawn',
|
||||
label: 'Einwilligung widerrufen',
|
||||
urgency: 'critical',
|
||||
details: `${withdrawn.size} Einwilligung(en) widerrufen`,
|
||||
});
|
||||
summary.byCategory.missingConsents++;
|
||||
customerConsentWarned.add(contract.customer.id);
|
||||
}
|
||||
else if (!granted || granted.size < requiredTypes.length) {
|
||||
// Nicht alle 4 Einwilligungen erteilt
|
||||
const missing = requiredTypes.length - (granted?.size || 0);
|
||||
issues.push({
|
||||
type: 'missing_consents',
|
||||
label: 'Fehlende Einwilligungen',
|
||||
urgency: 'critical',
|
||||
details: `${missing} von ${requiredTypes.length} Einwilligungen fehlen`,
|
||||
});
|
||||
summary.byCategory.missingConsents++;
|
||||
customerConsentWarned.add(contract.customer.id);
|
||||
}
|
||||
}
|
||||
// Nur Verträge mit Issues hinzufügen
|
||||
if (issues.length > 0) {
|
||||
const highestUrgency = getHighestUrgency(issues);
|
||||
@@ -523,8 +607,14 @@ async function getCockpitData() {
|
||||
};
|
||||
return urgencyOrder[a.highestUrgency] - urgencyOrder[b.highestUrgency];
|
||||
});
|
||||
// Vertragsunabhängige Ausweis-Warnungen
|
||||
const documentAlerts = await getDocumentExpiryAlerts(docExpiryCriticalDays, docExpiryWarningDays);
|
||||
// Gemeldete Zählerstände (REPORTED Status)
|
||||
const reportedReadings = await getReportedMeterReadings();
|
||||
return {
|
||||
contracts: cockpitContracts,
|
||||
documentAlerts,
|
||||
reportedReadings,
|
||||
summary,
|
||||
thresholds: {
|
||||
criticalDays,
|
||||
@@ -533,4 +623,106 @@ async function getCockpitData() {
|
||||
},
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Alle aktiven Ausweise die ablaufen oder abgelaufen sind (vertragsunabhängig)
|
||||
*/
|
||||
async function getDocumentExpiryAlerts(criticalDays, warningDays) {
|
||||
const now = new Date();
|
||||
const inWarningDays = new Date(now.getTime() + warningDays * 24 * 60 * 60 * 1000);
|
||||
const documents = await prisma.identityDocument.findMany({
|
||||
where: {
|
||||
isActive: true,
|
||||
expiryDate: { lte: inWarningDays },
|
||||
},
|
||||
include: {
|
||||
customer: {
|
||||
select: { id: true, customerNumber: true, firstName: true, lastName: true },
|
||||
},
|
||||
},
|
||||
orderBy: { expiryDate: 'asc' },
|
||||
});
|
||||
return documents.map((doc) => {
|
||||
const expiryDate = new Date(doc.expiryDate);
|
||||
const daysUntilExpiry = Math.ceil((expiryDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
|
||||
let urgency = 'warning';
|
||||
if (daysUntilExpiry < 0)
|
||||
urgency = 'critical';
|
||||
else if (daysUntilExpiry <= criticalDays)
|
||||
urgency = 'critical';
|
||||
return {
|
||||
id: doc.id,
|
||||
type: doc.type,
|
||||
documentNumber: doc.documentNumber,
|
||||
expiryDate: expiryDate.toISOString(),
|
||||
daysUntilExpiry,
|
||||
urgency,
|
||||
customer: {
|
||||
id: doc.customer.id,
|
||||
customerNumber: doc.customer.customerNumber,
|
||||
name: `${doc.customer.firstName} ${doc.customer.lastName}`,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Vom Kunden gemeldete Zählerstände die noch nicht übertragen wurden
|
||||
*/
|
||||
async function getReportedMeterReadings() {
|
||||
const readings = await prisma.meterReading.findMany({
|
||||
where: { status: 'REPORTED' },
|
||||
include: {
|
||||
meter: {
|
||||
include: {
|
||||
customer: {
|
||||
select: { id: true, customerNumber: true, firstName: true, lastName: true },
|
||||
},
|
||||
// Energie-Verträge für diesen Zähler (um Provider-Portal-Daten zu bekommen)
|
||||
energyDetails: {
|
||||
include: {
|
||||
contract: {
|
||||
select: {
|
||||
id: true,
|
||||
portalUsername: true,
|
||||
provider: {
|
||||
select: { id: true, name: true, portalUrl: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
take: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: { createdAt: 'asc' },
|
||||
});
|
||||
return readings.map((r) => {
|
||||
const contract = r.meter.energyDetails?.[0]?.contract;
|
||||
const provider = contract?.provider;
|
||||
return {
|
||||
id: r.id,
|
||||
readingDate: r.readingDate.toISOString(),
|
||||
value: r.value,
|
||||
unit: r.unit,
|
||||
notes: r.notes ?? undefined,
|
||||
reportedBy: r.reportedBy ?? undefined,
|
||||
createdAt: r.createdAt.toISOString(),
|
||||
meter: {
|
||||
id: r.meter.id,
|
||||
meterNumber: r.meter.meterNumber,
|
||||
type: r.meter.type,
|
||||
},
|
||||
customer: {
|
||||
id: r.meter.customer.id,
|
||||
customerNumber: r.meter.customer.customerNumber,
|
||||
name: `${r.meter.customer.firstName} ${r.meter.customer.lastName}`,
|
||||
},
|
||||
providerPortal: provider?.portalUrl ? {
|
||||
providerName: provider.name,
|
||||
portalUrl: provider.portalUrl,
|
||||
portalUsername: contract?.portalUsername ?? undefined,
|
||||
} : undefined,
|
||||
};
|
||||
});
|
||||
}
|
||||
//# sourceMappingURL=contractCockpit.service.js.map
|
||||
Reference in New Issue
Block a user