428 lines
16 KiB
JavaScript
428 lines
16 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
|
|
// Für DSL, FIBER, CABLE, MOBILE ist dies ein kritisches Problem
|
|
const requiresBankAndId = ['DSL', 'FIBER', 'CABLE', 'MOBILE'].includes(contract.type);
|
|
if (!contract.bankCardId) {
|
|
issues.push({
|
|
type: 'missing_bank',
|
|
label: 'Bankverbindung fehlt',
|
|
urgency: requiresBankAndId ? 'critical' : 'warning',
|
|
details: 'Keine Bankverbindung verknüpft',
|
|
});
|
|
summary.byCategory.missingData++;
|
|
}
|
|
// 7b. KEIN AUSWEIS (für DSL, FIBER, CABLE, MOBILE ist dies ein kritisches Problem)
|
|
if (!contract.identityDocumentId) {
|
|
issues.push({
|
|
type: 'missing_identity_document',
|
|
label: 'Ausweis fehlt',
|
|
urgency: requiresBankAndId ? 'critical' : 'warning',
|
|
details: 'Kein Ausweisdokument 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
|