"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getAllCustomers = getAllCustomers; exports.getCustomerById = getCustomerById; exports.getCustomersByIds = getCustomersByIds; exports.createCustomer = createCustomer; exports.updateCustomer = updateCustomer; exports.deleteCustomer = deleteCustomer; exports.getCustomerAddresses = getCustomerAddresses; exports.createAddress = createAddress; exports.updateAddress = updateAddress; exports.deleteAddress = deleteAddress; exports.getCustomerBankCards = getCustomerBankCards; exports.createBankCard = createBankCard; exports.updateBankCard = updateBankCard; exports.deleteBankCard = deleteBankCard; exports.getCustomerDocuments = getCustomerDocuments; exports.createDocument = createDocument; exports.updateDocument = updateDocument; exports.deleteDocument = deleteDocument; exports.getCustomerMeters = getCustomerMeters; exports.createMeter = createMeter; exports.updateMeter = updateMeter; exports.deleteMeter = deleteMeter; exports.addMeterReading = addMeterReading; exports.getMeterReadings = getMeterReadings; exports.updateMeterReading = updateMeterReading; exports.deleteMeterReading = deleteMeterReading; exports.updatePortalSettings = updatePortalSettings; exports.getPortalSettings = getPortalSettings; exports.getCustomerRepresentatives = getCustomerRepresentatives; exports.getRepresentedByList = getRepresentedByList; exports.addRepresentative = addRepresentative; exports.removeRepresentative = removeRepresentative; exports.searchCustomersForRepresentative = searchCustomersForRepresentative; const client_1 = require("@prisma/client"); const prisma_js_1 = __importDefault(require("../lib/prisma.js")); const helpers_js_1 = require("../utils/helpers.js"); const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); // Helper zum Löschen von Dateien function deleteFileIfExists(filePath) { if (!filePath) return; const absolutePath = path_1.default.join(process.cwd(), filePath); if (fs_1.default.existsSync(absolutePath)) { try { fs_1.default.unlinkSync(absolutePath); } catch (error) { console.error('Fehler beim Löschen der Datei:', absolutePath, error); } } } async function getAllCustomers(filters) { const { search, type, page = 1, limit = 20 } = filters; const { skip, take } = (0, helpers_js_1.paginate)(page, limit); const where = {}; if (type) { where.type = type; } if (search) { where.OR = [ { firstName: { contains: search } }, { lastName: { contains: search } }, { companyName: { contains: search } }, { email: { contains: search } }, { customerNumber: { contains: search } }, ]; } const [customers, total] = await Promise.all([ prisma_js_1.default.customer.findMany({ where, skip, take, orderBy: { createdAt: 'desc' }, include: { addresses: { where: { isDefault: true }, take: 1 }, _count: { select: { contracts: true }, }, }, }), prisma_js_1.default.customer.count({ where }), ]); return { customers, pagination: (0, helpers_js_1.buildPaginationResponse)(page, limit, total), }; } async function getCustomerById(id) { return prisma_js_1.default.customer.findUnique({ where: { id }, include: { addresses: true, bankCards: { orderBy: { isActive: 'desc' } }, identityDocuments: { orderBy: { isActive: 'desc' } }, meters: { orderBy: { isActive: 'desc' }, include: { readings: { orderBy: { readingDate: 'desc' }, }, }, }, stressfreiEmails: { orderBy: { isActive: 'desc' } }, contracts: { where: { // Deaktivierte Verträge ausblenden status: { not: client_1.ContractStatus.DEACTIVATED }, }, orderBy: [{ startDate: 'desc' }, { createdAt: 'desc' }], include: { address: true, salesPlatform: true, }, }, }, }); } async function getCustomersByIds(ids) { return prisma_js_1.default.customer.findMany({ where: { id: { in: ids } }, select: { id: true, portalEmail: true, }, }); } async function createCustomer(data) { return prisma_js_1.default.customer.create({ data: { ...data, customerNumber: (0, helpers_js_1.generateCustomerNumber)(), }, }); } async function updateCustomer(id, data) { return prisma_js_1.default.customer.update({ where: { id }, data, }); } async function deleteCustomer(id) { // Vor dem Löschen: Alle Dokumente (Dateien) des Kunden löschen const customer = await prisma_js_1.default.customer.findUnique({ where: { id }, select: { businessRegistrationPath: true, commercialRegisterPath: true, privacyPolicyPath: true }, }); const bankCards = await prisma_js_1.default.bankCard.findMany({ where: { customerId: id }, select: { documentPath: true }, }); const identityDocs = await prisma_js_1.default.identityDocument.findMany({ where: { customerId: id }, select: { documentPath: true }, }); // Kundendokumente löschen if (customer) { deleteFileIfExists(customer.businessRegistrationPath); deleteFileIfExists(customer.commercialRegisterPath); deleteFileIfExists(customer.privacyPolicyPath); } // Bankkarten- und Ausweisdokumente löschen for (const card of bankCards) { deleteFileIfExists(card.documentPath); } for (const doc of identityDocs) { deleteFileIfExists(doc.documentPath); } // Jetzt DB-Eintrag löschen (Cascade löscht die verknüpften Einträge) return prisma_js_1.default.customer.delete({ where: { id }, }); } // Address operations async function getCustomerAddresses(customerId) { return prisma_js_1.default.address.findMany({ where: { customerId }, orderBy: [{ isDefault: 'desc' }, { createdAt: 'desc' }], }); } async function createAddress(customerId, data) { // If this is set as default, unset other defaults of same type if (data.isDefault) { await prisma_js_1.default.address.updateMany({ where: { customerId, type: data.type }, data: { isDefault: false }, }); } return prisma_js_1.default.address.create({ data: { customerId, ...data, }, }); } async function updateAddress(id, data) { const address = await prisma_js_1.default.address.findUnique({ where: { id } }); if (!address) throw new Error('Adresse nicht gefunden'); if (data.isDefault) { await prisma_js_1.default.address.updateMany({ where: { customerId: address.customerId, type: data.type || address.type, id: { not: id }, }, data: { isDefault: false }, }); } return prisma_js_1.default.address.update({ where: { id }, data, }); } async function deleteAddress(id) { return prisma_js_1.default.address.delete({ where: { id } }); } // Bank card operations async function getCustomerBankCards(customerId, showInactive = false) { const where = { customerId }; if (!showInactive) { where.isActive = true; } return prisma_js_1.default.bankCard.findMany({ where, orderBy: [{ isActive: 'desc' }, { createdAt: 'desc' }], }); } async function createBankCard(customerId, data) { return prisma_js_1.default.bankCard.create({ data: { customerId, ...data, isActive: true, }, }); } async function updateBankCard(id, data) { return prisma_js_1.default.bankCard.update({ where: { id }, data, }); } async function deleteBankCard(id) { // Erst Datei-Pfad holen, dann Datei löschen, dann DB-Eintrag löschen const bankCard = await prisma_js_1.default.bankCard.findUnique({ where: { id } }); if (bankCard?.documentPath) { deleteFileIfExists(bankCard.documentPath); } return prisma_js_1.default.bankCard.delete({ where: { id } }); } // Identity document operations async function getCustomerDocuments(customerId, showInactive = false) { const where = { customerId }; if (!showInactive) { where.isActive = true; } return prisma_js_1.default.identityDocument.findMany({ where, orderBy: [{ isActive: 'desc' }, { createdAt: 'desc' }], }); } async function createDocument(customerId, data) { return prisma_js_1.default.identityDocument.create({ data: { customerId, ...data, isActive: true, }, }); } async function updateDocument(id, data) { return prisma_js_1.default.identityDocument.update({ where: { id }, data, }); } async function deleteDocument(id) { // Erst Datei-Pfad holen, dann Datei löschen, dann DB-Eintrag löschen const document = await prisma_js_1.default.identityDocument.findUnique({ where: { id } }); if (document?.documentPath) { deleteFileIfExists(document.documentPath); } return prisma_js_1.default.identityDocument.delete({ where: { id } }); } // Meter operations async function getCustomerMeters(customerId, showInactive = false) { const where = { customerId }; if (!showInactive) { where.isActive = true; } return prisma_js_1.default.meter.findMany({ where, include: { readings: { orderBy: { readingDate: 'desc' }, take: 5, }, }, orderBy: [{ isActive: 'desc' }, { createdAt: 'desc' }], }); } async function createMeter(customerId, data) { return prisma_js_1.default.meter.create({ data: { customerId, ...data, isActive: true, }, }); } async function updateMeter(id, data) { return prisma_js_1.default.meter.update({ where: { id }, data, }); } async function deleteMeter(id) { // Prüfen ob der Zähler noch an Verträgen hängt const linkedContracts = await prisma_js_1.default.contractMeter.findMany({ where: { meterId: id }, include: { energyContractDetails: { include: { contract: { select: { contractNumber: true } } } } }, }); if (linkedContracts.length > 0) { const contractNumbers = linkedContracts .map(cm => cm.energyContractDetails.contract.contractNumber) .join(', '); throw new Error(`Zähler kann nicht gelöscht werden – noch an Vertrag/Verträgen zugeordnet: ${contractNumbers}`); } // Auch direkte meterId-Referenz auf EnergyContractDetails prüfen const directLinks = await prisma_js_1.default.energyContractDetails.findMany({ where: { meterId: id }, include: { contract: { select: { contractNumber: true } } }, }); if (directLinks.length > 0) { const contractNumbers = directLinks.map(d => d.contract.contractNumber).join(', '); throw new Error(`Zähler kann nicht gelöscht werden – noch an Vertrag/Verträgen zugeordnet: ${contractNumbers}`); } return prisma_js_1.default.meter.delete({ where: { id } }); } async function addMeterReading(meterId, data) { // Validierung: Zählerstand muss monoton steigend sein await validateReadingValue(meterId, data.readingDate, data.value, undefined, 'HT'); if (data.valueNt !== undefined) { await validateReadingValue(meterId, data.readingDate, data.valueNt, undefined, 'NT'); } return prisma_js_1.default.meterReading.create({ data: { meterId, readingDate: data.readingDate, value: data.value, valueNt: data.valueNt, unit: data.unit, notes: data.notes, }, }); } async function getMeterReadings(meterId) { return prisma_js_1.default.meterReading.findMany({ where: { meterId }, orderBy: { readingDate: 'desc' }, }); } async function updateMeterReading(meterId, readingId, data) { // Verify the reading belongs to the meter const reading = await prisma_js_1.default.meterReading.findFirst({ where: { id: readingId, meterId }, }); if (!reading) { throw new Error('Zählerstand nicht gefunden'); } // Validierung bei Wertänderung if (data.value !== undefined || data.readingDate !== undefined) { await validateReadingValue(meterId, data.readingDate || reading.readingDate, data.value ?? reading.value, readingId, 'HT'); } if (data.valueNt !== undefined || data.readingDate !== undefined) { const ntVal = data.valueNt ?? reading.valueNt; if (ntVal !== undefined && ntVal !== null) { await validateReadingValue(meterId, data.readingDate || reading.readingDate, ntVal, readingId, 'NT'); } } return prisma_js_1.default.meterReading.update({ where: { id: readingId }, data, }); } /** * Validiert, dass ein Zählerstand monoton steigend ist. * tariffLabel: 'HT' für Hochtarif/Eintarif, 'NT' für Niedertarif */ async function validateReadingValue(meterId, readingDate, value, excludeReadingId, tariffLabel = 'HT') { const existing = await prisma_js_1.default.meterReading.findMany({ where: { meterId, ...(excludeReadingId ? { id: { not: excludeReadingId } } : {}) }, orderBy: { readingDate: 'asc' }, }); const fmtDate = (d) => d.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' }); const fmtVal = (v) => v.toLocaleString('de-DE'); const label = tariffLabel === 'NT' ? 'NT-Zählerstand' : 'Zählerstand'; // Vergleichswert aus bestehendem Reading extrahieren const getVal = (r) => tariffLabel === 'NT' ? (r.valueNt ?? 0) : r.value; // Stand vor dem neuen Datum const before = [...existing].filter(r => r.readingDate <= readingDate).pop(); if (before && value < getVal(before)) { throw new Error(`${label} (${fmtVal(value)}) darf nicht kleiner sein als der Stand vom ${fmtDate(before.readingDate)} (${fmtVal(getVal(before))})`); } // Stand nach dem neuen Datum const after = existing.find(r => r.readingDate > readingDate); if (after && value > getVal(after)) { throw new Error(`${label} (${fmtVal(value)}) darf nicht größer sein als der spätere Stand vom ${fmtDate(after.readingDate)} (${fmtVal(getVal(after))})`); } } async function deleteMeterReading(meterId, readingId) { // Verify the reading belongs to the meter const reading = await prisma_js_1.default.meterReading.findFirst({ where: { id: readingId, meterId }, }); if (!reading) { throw new Error('Zählerstand nicht gefunden'); } return prisma_js_1.default.meterReading.delete({ where: { id: readingId }, }); } // ==================== PORTAL SETTINGS ==================== async function updatePortalSettings(customerId, data) { // Wenn Portal deaktiviert wird, Passwort-Hash nicht löschen (für spätere Reaktivierung) return prisma_js_1.default.customer.update({ where: { id: customerId }, data: { portalEnabled: data.portalEnabled, portalEmail: data.portalEmail, }, select: { id: true, portalEnabled: true, portalEmail: true, portalLastLogin: true, }, }); } async function getPortalSettings(customerId) { return prisma_js_1.default.customer.findUnique({ where: { id: customerId }, select: { id: true, portalEnabled: true, portalEmail: true, portalLastLogin: true, portalPasswordHash: true, // Nur um zu prüfen ob Passwort gesetzt (wird als boolean zurückgegeben) }, }); } // ==================== REPRESENTATIVE MANAGEMENT ==================== async function getCustomerRepresentatives(customerId) { // Holt alle Kunden, die der angegebene Kunde vertreten kann (dieser ist der Vertreter) return prisma_js_1.default.customerRepresentative.findMany({ where: { representativeId: customerId, isActive: true }, include: { customer: { select: { id: true, customerNumber: true, firstName: true, lastName: true, companyName: true, type: true, }, }, }, orderBy: { createdAt: 'desc' }, }); } async function getRepresentedByList(customerId) { // Holt alle Kunden, die den angegebenen Kunden vertreten können return prisma_js_1.default.customerRepresentative.findMany({ where: { customerId: customerId, isActive: true }, include: { representative: { select: { id: true, customerNumber: true, firstName: true, lastName: true, companyName: true, type: true, }, }, }, orderBy: { createdAt: 'desc' }, }); } async function addRepresentative(customerId, // Der Kunde, dessen Verträge eingesehen werden dürfen representativeId, // Der Kunde, der einsehen darf notes) { // Prüfen, ob beide Kunden existieren const [customer, representative] = await Promise.all([ prisma_js_1.default.customer.findUnique({ where: { id: customerId } }), prisma_js_1.default.customer.findUnique({ where: { id: representativeId } }), ]); if (!customer) { throw new Error('Kunde nicht gefunden'); } if (!representative) { throw new Error('Vertreter-Kunde nicht gefunden'); } if (customerId === representativeId) { throw new Error('Ein Kunde kann sich nicht selbst vertreten'); } // Prüfen ob der Vertreter ein Portal-Konto hat if (!representative.portalEnabled) { throw new Error('Der Vertreter-Kunde muss ein aktiviertes Portal-Konto haben'); } return prisma_js_1.default.customerRepresentative.upsert({ where: { customerId_representativeId: { customerId, representativeId }, }, create: { customerId, representativeId, notes, isActive: true, }, update: { isActive: true, notes, }, include: { representative: { select: { id: true, customerNumber: true, firstName: true, lastName: true, companyName: true, type: true, }, }, }, }); } async function removeRepresentative(customerId, representativeId) { // Anstatt zu löschen, setzen wir isActive auf false return prisma_js_1.default.customerRepresentative.update({ where: { customerId_representativeId: { customerId, representativeId }, }, data: { isActive: false }, }); } async function searchCustomersForRepresentative(search, excludeCustomerId) { // Sucht Kunden, die als Vertreter hinzugefügt werden können // Nur Kunden mit aktiviertem Portal return prisma_js_1.default.customer.findMany({ where: { id: { not: excludeCustomerId }, portalEnabled: true, OR: [ { firstName: { contains: search } }, { lastName: { contains: search } }, { companyName: { contains: search } }, { customerNumber: { contains: search } }, ], }, select: { id: true, customerNumber: true, firstName: true, lastName: true, companyName: true, type: true, }, take: 10, }); } //# sourceMappingURL=customer.service.js.map