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

728 lines
30 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;
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: {
status: {
in: ['ACTIVE', 'PENDING', 'DRAFT', 'CANCELLED', 'DEACTIVATED', 'EXPIRED'],
},
},
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,
invoices: 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,
missingInvoices: 0,
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)
let snoozeActive = false;
if (contract.nextReviewDate) {
const reviewDate = new Date(contract.nextReviewDate);
const now = new Date();
now.setHours(0, 0, 0, 0);
reviewDate.setHours(0, 0, 0, 0);
if (reviewDate > now) {
// Snooze aktiv → NUR Fristen-Warnungen unterdrücken, andere Prüfungen laufen weiter
snoozeActive = true;
}
else {
// Snooze abgelaufen → "Erneute Prüfung fällig" Warnung
const daysSince = Math.floor((now.getTime() - reviewDate.getTime()) / (1000 * 60 * 60 * 24));
issues.push({
type: 'review_due',
label: 'Erneute Prüfung fällig',
urgency: daysSince > 30 ? 'critical' : 'warning',
daysRemaining: -daysSince,
details: daysSince === 0
? 'Heute zur Prüfung fällig'
: `Zur Prüfung seit ${daysSince} Tagen fällig`,
});
summary.byCategory.reviewDue++;
}
}
// 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 UND Snooze nicht aktiv)
// Snooze unterdrückt NUR Fristen-bezogene Warnungen!
if (!hasActiveFollowUp && !snoozeActive) {
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 (nur wenn Snooze nicht aktiv)
// Snooze unterdrückt NUR Fristen-bezogene Warnungen!
if (!snoozeActive) {
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
// Benutzername kann entweder manuell (portalUsername) oder via Stressfrei-Wechseln E-Mail (stressfreiEmailId) gesetzt sein
const hasUsername = contract.portalUsername || contract.stressfreiEmailId;
const hasPassword = contract.portalPasswordEncrypted;
if (!hasUsername || !hasPassword) {
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++;
}
// 4b. KEINE VERTRAGSNUMMER BEIM ANBIETER
if (!contract.contractNumberAtProvider) {
issues.push({
type: 'missing_contract_number',
label: 'Vertragsnummer fehlt',
urgency: 'warning',
details: 'Vertragsnummer 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 (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: '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) {
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++;
}
// 13. ENERGIE-RECHNUNGEN (nur für ELECTRICITY und GAS)
if (['ELECTRICITY', 'GAS'].includes(contract.type) && contract.energyDetails) {
const invoices = contract.energyDetails.invoices || [];
const now = new Date();
now.setHours(0, 0, 0, 0);
// 13a. SCHLUSSRECHNUNG FEHLT (nur wenn Vertrag gekündigt/deaktiviert ist)
// "Beendet" = CANCELLED oder DEACTIVATED (nicht nur Laufzeit abgelaufen!)
const isContractTerminated = contract.status === 'CANCELLED' || contract.status === 'DEACTIVATED';
if (isContractTerminated) {
const hasFinalInvoice = invoices.some(inv => inv.invoiceType === 'FINAL');
const hasNotAvailable = invoices.some(inv => inv.invoiceType === 'NOT_AVAILABLE');
if (!hasFinalInvoice && !hasNotAvailable) {
issues.push({
type: 'missing_final_invoice',
label: 'Schlussrechnung fehlt',
urgency: 'warning',
details: 'Vertrag gekündigt/deaktiviert, aber keine Schlussrechnung vorhanden',
});
summary.byCategory.missingInvoices++;
}
}
// 13b. ZWISCHENRECHNUNG FEHLT/ÜBERFÄLLIG (wenn Vertrag > 12 Monate läuft)
// Für alle Status außer DRAFT und nicht gekündigt/deaktiviert
// Auch EXPIRED zählt hier, da der Vertrag ohne Kündigung weiterläuft!
if (contract.startDate && contract.status !== 'DRAFT' && !isContractTerminated) {
const startDate = new Date(contract.startDate);
startDate.setHours(0, 0, 0, 0);
const daysSinceStart = Math.floor((now.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24));
if (daysSinceStart > 365) {
// Vertrag läuft > 12 Monate
if (invoices.length === 0) {
// Keine Rechnungen vorhanden
issues.push({
type: 'missing_interim_invoice',
label: 'Zwischenrechnung fehlt',
urgency: 'warning',
details: 'Vertrag läuft über 12 Monate ohne Rechnung',
});
summary.byCategory.missingInvoices++;
}
else {
// Prüfen ob letzte Rechnung > 12 Monate alt
const latestInvoice = invoices
.filter(inv => inv.invoiceType !== 'NOT_AVAILABLE')
.sort((a, b) => new Date(b.invoiceDate).getTime() - new Date(a.invoiceDate).getTime())[0];
if (latestInvoice) {
const invoiceDate = new Date(latestInvoice.invoiceDate);
const daysSinceInvoice = Math.floor((now.getTime() - invoiceDate.getTime()) / (1000 * 60 * 60 * 24));
if (daysSinceInvoice > 365) {
issues.push({
type: 'overdue_interim_invoice',
label: 'Zwischenrechnung überfällig',
urgency: 'warning',
details: `Letzte Rechnung vor ${Math.floor(daysSinceInvoice / 30)} Monaten`,
});
summary.byCategory.missingInvoices++;
}
}
}
}
}
}
// #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);
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];
});
// 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,
warningDays,
okDays,
},
};
}
/**
* 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