opencrm/backend/dist/services/contractCockpit.service.js

416 lines
15 KiB
JavaScript

"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.getCockpitData = getCockpitData;
const client_1 = require("@prisma/client");
const appSettingService = __importStar(require("./appSetting.service.js"));
const prisma = new client_1.PrismaClient();
// Hilfsfunktion: Tage bis zu einem Datum berechnen
function daysUntil(date) {
if (!date)
return null;
const now = new Date();
now.setHours(0, 0, 0, 0);
const target = new Date(date);
target.setHours(0, 0, 0, 0);
const diff = target.getTime() - now.getTime();
return Math.ceil(diff / (1000 * 60 * 60 * 24));
}
// Hilfsfunktion: Urgency basierend auf Tagen bestimmen
function getUrgencyByDays(daysRemaining, criticalDays, warningDays, okDays) {
if (daysRemaining === null)
return 'none';
if (daysRemaining < 0)
return 'critical'; // Bereits überfällig
if (daysRemaining <= criticalDays)
return 'critical';
if (daysRemaining <= warningDays)
return 'warning';
if (daysRemaining <= okDays)
return 'ok';
return 'none';
}
// Hilfsfunktion: Höchste Dringlichkeit ermitteln
function getHighestUrgency(issues) {
const levels = ['critical', 'warning', 'ok', 'none'];
for (const level of levels) {
if (issues.some(i => i.urgency === level)) {
return level;
}
}
return 'none';
}
// Kündigungsfrist berechnen
function calculateCancellationDeadline(endDate, cancellationPeriodCode) {
if (!endDate || !cancellationPeriodCode)
return null;
const end = new Date(endDate);
// Parse Kündigungsperiode (z.B. "1M" = 1 Monat, "6W" = 6 Wochen, "14D" = 14 Tage)
const match = cancellationPeriodCode.match(/^(\d+)([DMWY])$/i);
if (!match)
return null;
const amount = parseInt(match[1]);
const unit = match[2].toUpperCase();
switch (unit) {
case 'D':
end.setDate(end.getDate() - amount);
break;
case 'W':
end.setDate(end.getDate() - (amount * 7));
break;
case 'M':
end.setMonth(end.getMonth() - amount);
break;
case 'Y':
end.setFullYear(end.getFullYear() - amount);
break;
}
return end;
}
async function getCockpitData() {
// Lade Einstellungen
const settings = await appSettingService.getAllSettings();
const criticalDays = parseInt(settings.deadlineCriticalDays) || 14;
const warningDays = parseInt(settings.deadlineWarningDays) || 42;
const okDays = parseInt(settings.deadlineOkDays) || 90;
// Lade alle aktiven/pending Verträge mit allen relevanten Daten
const contracts = await prisma.contract.findMany({
where: {
status: {
in: ['ACTIVE', 'PENDING', 'DRAFT'],
},
},
include: {
customer: {
select: {
id: true,
customerNumber: true,
firstName: true,
lastName: true,
companyName: true,
},
},
provider: {
select: {
id: true,
name: true,
},
},
tariff: {
select: {
id: true,
name: true,
},
},
cancellationPeriod: {
select: {
code: true,
},
},
address: true,
bankCard: true,
identityDocument: true,
energyDetails: {
include: {
meter: true,
},
},
internetDetails: {
include: {
phoneNumbers: true,
},
},
mobileDetails: {
include: {
simCards: true,
},
},
tasks: {
where: {
status: 'OPEN',
},
},
// Folgevertrag laden um zu prüfen ob dieser aktiv ist
followUpContract: {
select: {
id: true,
status: true,
},
},
},
orderBy: [
{ endDate: 'asc' },
{ createdAt: 'desc' },
],
});
const cockpitContracts = [];
const summary = {
totalContracts: 0,
criticalCount: 0,
warningCount: 0,
okCount: 0,
byCategory: {
cancellationDeadlines: 0,
contractEnding: 0,
missingCredentials: 0,
missingData: 0,
openTasks: 0,
pendingContracts: 0,
},
};
for (const contract of contracts) {
const issues = [];
// Prüfen ob aktiver Folgevertrag existiert - dann keine Kündigungswarnungen nötig
const hasActiveFollowUp = contract.followUpContract?.status === 'ACTIVE';
// 1. KÜNDIGUNGSFRIST (nur wenn kein aktiver Folgevertrag)
if (!hasActiveFollowUp) {
const cancellationDeadline = calculateCancellationDeadline(contract.endDate, contract.cancellationPeriod?.code);
const daysToCancellation = daysUntil(cancellationDeadline);
if (daysToCancellation !== null && daysToCancellation <= okDays) {
const urgency = getUrgencyByDays(daysToCancellation, criticalDays, warningDays, okDays);
if (urgency !== 'none') {
issues.push({
type: 'cancellation_deadline',
label: 'Kündigungsfrist',
urgency,
daysRemaining: daysToCancellation,
details: daysToCancellation < 0
? `Frist seit ${Math.abs(daysToCancellation)} Tagen überschritten!`
: `Noch ${daysToCancellation} Tage bis zur Kündigungsfrist`,
});
summary.byCategory.cancellationDeadlines++;
}
// 1a. KÜNDIGUNG NICHT GESENDET (wenn Frist naht)
if (!contract.cancellationLetterPath) {
issues.push({
type: 'missing_cancellation_letter',
label: 'Kündigung nicht gesendet',
urgency,
details: 'Kündigungsschreiben wurde noch nicht hochgeladen',
});
summary.byCategory.missingData++;
}
// 1b. KÜNDIGUNGSBESTÄTIGUNG FEHLT (wenn Kündigung gesendet aber keine Bestätigung)
if (contract.cancellationLetterPath && !contract.cancellationConfirmationPath && !contract.cancellationConfirmationDate) {
issues.push({
type: 'missing_cancellation_confirmation',
label: 'Kündigungsbestätigung fehlt',
urgency: urgency === 'critical' ? 'critical' : 'warning',
details: 'Kündigungsbestätigung vom Anbieter wurde noch nicht erhalten',
});
summary.byCategory.missingData++;
}
}
}
// 2. VERTRAGSENDE
const daysToEnd = daysUntil(contract.endDate);
if (daysToEnd !== null && daysToEnd <= okDays) {
const urgency = getUrgencyByDays(daysToEnd, criticalDays, warningDays, okDays);
if (urgency !== 'none') {
issues.push({
type: 'contract_ending',
label: 'Vertragsende',
urgency,
daysRemaining: daysToEnd,
details: daysToEnd < 0
? `Vertrag seit ${Math.abs(daysToEnd)} Tagen abgelaufen!`
: `Noch ${daysToEnd} Tage bis Vertragsende`,
});
summary.byCategory.contractEnding++;
}
}
// 3. FEHLENDE PORTAL-ZUGANGSDATEN
if (!contract.portalUsername || !contract.portalPasswordEncrypted) {
issues.push({
type: 'missing_portal_credentials',
label: 'Portal-Zugangsdaten fehlen',
urgency: 'warning',
details: 'Benutzername oder Passwort für das Anbieter-Portal fehlt',
});
summary.byCategory.missingCredentials++;
}
// 4. KEINE KUNDENNUMMER BEIM ANBIETER
if (!contract.customerNumberAtProvider) {
issues.push({
type: 'missing_customer_number',
label: 'Kundennummer fehlt',
urgency: 'warning',
details: 'Kundennummer beim Anbieter fehlt',
});
summary.byCategory.missingData++;
}
// 5. KEIN ANBIETER/TARIF
if (!contract.providerId && !contract.providerName) {
issues.push({
type: 'missing_provider',
label: 'Anbieter fehlt',
urgency: 'warning',
details: 'Kein Anbieter ausgewählt',
});
summary.byCategory.missingData++;
}
// 6. KEINE ADRESSE
if (!contract.addressId) {
issues.push({
type: 'missing_address',
label: 'Adresse fehlt',
urgency: 'warning',
details: 'Keine Lieferadresse verknüpft',
});
summary.byCategory.missingData++;
}
// 7. KEINE BANKVERBINDUNG
if (!contract.bankCardId) {
issues.push({
type: 'missing_bank',
label: 'Bankverbindung fehlt',
urgency: 'warning',
details: 'Keine Bankverbindung verknüpft',
});
summary.byCategory.missingData++;
}
// 8. ENERGIE-SPEZIFISCH: KEIN ZÄHLER
if (['ELECTRICITY', 'GAS'].includes(contract.type) && contract.energyDetails) {
if (!contract.energyDetails.meterId) {
issues.push({
type: 'missing_meter',
label: 'Zähler fehlt',
urgency: 'warning',
details: 'Kein Zähler verknüpft',
});
summary.byCategory.missingData++;
}
}
// 9. MOBIL-SPEZIFISCH: SIM-KARTEN
if (contract.type === 'MOBILE' && contract.mobileDetails) {
if (!contract.mobileDetails.simCards || contract.mobileDetails.simCards.length === 0) {
issues.push({
type: 'missing_sim',
label: 'SIM-Karte fehlt',
urgency: 'warning',
details: 'Keine SIM-Karte eingetragen',
});
summary.byCategory.missingData++;
}
}
// 10. OFFENE AUFGABEN
if (contract.tasks && contract.tasks.length > 0) {
issues.push({
type: 'open_tasks',
label: 'Offene Aufgaben',
urgency: 'ok',
details: `${contract.tasks.length} offene Aufgabe(n)`,
});
summary.byCategory.openTasks++;
}
// 11. PENDING STATUS
if (contract.status === 'PENDING') {
issues.push({
type: 'pending_status',
label: 'Warte auf Aktivierung',
urgency: 'ok',
details: 'Vertrag noch nicht aktiv',
});
summary.byCategory.pendingContracts++;
}
// 12. DRAFT STATUS
if (contract.status === 'DRAFT') {
issues.push({
type: 'draft_status',
label: 'Entwurf',
urgency: 'warning',
details: 'Vertrag ist noch ein Entwurf',
});
summary.byCategory.pendingContracts++;
}
// Nur Verträge mit Issues hinzufügen
if (issues.length > 0) {
const highestUrgency = getHighestUrgency(issues);
const customerName = contract.customer.companyName ||
`${contract.customer.firstName} ${contract.customer.lastName}`;
cockpitContracts.push({
id: contract.id,
contractNumber: contract.contractNumber,
type: contract.type,
status: contract.status,
customer: {
id: contract.customer.id,
customerNumber: contract.customer.customerNumber,
name: customerName,
},
provider: contract.provider ? {
id: contract.provider.id,
name: contract.provider.name,
} : undefined,
tariff: contract.tariff ? {
id: contract.tariff.id,
name: contract.tariff.name,
} : undefined,
providerName: contract.providerName || undefined,
tariffName: contract.tariffName || undefined,
issues,
highestUrgency,
});
// Summary zählen
summary.totalContracts++;
if (highestUrgency === 'critical')
summary.criticalCount++;
else if (highestUrgency === 'warning')
summary.warningCount++;
else if (highestUrgency === 'ok')
summary.okCount++;
}
}
// Sortiere nach Dringlichkeit
cockpitContracts.sort((a, b) => {
const urgencyOrder = {
critical: 0,
warning: 1,
ok: 2,
none: 3,
};
return urgencyOrder[a.highestUrgency] - urgencyOrder[b.highestUrgency];
});
return {
contracts: cockpitContracts,
summary,
thresholds: {
criticalDays,
warningDays,
okDays,
},
};
}
//# sourceMappingURL=contractCockpit.service.js.map