first commit
This commit is contained in:
+416
@@ -0,0 +1,416 @@
|
||||
"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
|
||||
Reference in New Issue
Block a user