import { CustomerType, ContractStatus } from '@prisma/client'; import prisma from '../lib/prisma.js'; import { generateCustomerNumber, paginate, buildPaginationResponse } from '../utils/helpers.js'; import fs from 'fs'; import path from 'path'; // Helper zum Löschen von Dateien function deleteFileIfExists(filePath: string | null) { if (!filePath) return; const absolutePath = path.join(process.cwd(), filePath); if (fs.existsSync(absolutePath)) { try { fs.unlinkSync(absolutePath); } catch (error) { console.error('Fehler beim Löschen der Datei:', absolutePath, error); } } } export interface CustomerFilters { search?: string; type?: CustomerType; page?: number; limit?: number; // Wenn gesetzt: nur Customer mit id in dieser Liste. Für Portal-User, damit // weder Liste noch pagination.total die globale Kunden-Zahl preisgibt. allowedIds?: number[]; } export async function getAllCustomers(filters: CustomerFilters) { const { search, type, page = 1, limit = 20, allowedIds } = filters; const { skip, take } = paginate(page, limit); const where: Record = {}; if (type) { where.type = type; } if (allowedIds) { where.id = { in: allowedIds }; } 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.customer.findMany({ where, skip, take, orderBy: { createdAt: 'desc' }, include: { addresses: { where: { isDefault: true }, take: 1 }, _count: { select: { contracts: true }, }, }, }), prisma.customer.count({ where }), ]); return { customers, pagination: buildPaginationResponse(page, limit, total), }; } export async function getCustomerById(id: number) { return prisma.customer.findUnique({ where: { id }, include: { addresses: true, bankCards: { orderBy: { isActive: 'desc' } }, identityDocuments: { orderBy: { isActive: 'desc' } }, meters: { orderBy: { isActive: 'desc' }, include: { address: true, readings: { orderBy: { readingDate: 'desc' }, }, // Verträge, die diesen Zähler aktuell als Hauptzähler nutzen // (energyDetails.meterId === meter.id) energyDetails: { include: { contract: { select: { id: true, contractNumber: true, status: true, type: true, providerName: true } }, }, }, // Verträge, in denen der Zähler in der ContractMeter-Kette steht // (Vorgänger oder Nachfolger über Zählerwechsel) contractMeters: { include: { energyContractDetails: { include: { contract: { select: { id: true, contractNumber: true, status: true, type: true, providerName: true } }, }, }, }, }, }, }, stressfreiEmails: { orderBy: { isActive: 'desc' } }, contracts: { where: { // Deaktivierte Verträge ausblenden status: { not: ContractStatus.DEACTIVATED }, }, orderBy: [{ startDate: 'desc' }, { createdAt: 'desc' }], include: { address: true, salesPlatform: true, }, }, }, }); } export async function getCustomersByIds(ids: number[]) { return prisma.customer.findMany({ where: { id: { in: ids } }, select: { id: true, portalEmail: true, }, }); } export async function createCustomer(data: { type?: CustomerType; salutation?: string; firstName: string; lastName: string; companyName?: string; birthDate?: Date; birthPlace?: string; email?: string; phone?: string; mobile?: string; taxNumber?: string; businessRegistration?: string; commercialRegister?: string; notes?: string; }) { return prisma.customer.create({ data: { ...data, customerNumber: generateCustomerNumber(), }, }); } export async function updateCustomer( id: number, data: { type?: CustomerType; salutation?: string; useInformalAddress?: boolean; firstName?: string; lastName?: string; companyName?: string; birthDate?: Date; birthPlace?: string; email?: string; phone?: string; mobile?: string; taxNumber?: string; businessRegistration?: string; commercialRegister?: string; notes?: string; autoBirthdayGreeting?: boolean; autoBirthdayChannel?: string | null; } ) { return prisma.customer.update({ where: { id }, data, }); } export async function deleteCustomer(id: number) { // Vor dem Löschen: Alle Dokumente (Dateien) des Kunden löschen const customer = await prisma.customer.findUnique({ where: { id }, select: { businessRegistrationPath: true, commercialRegisterPath: true, privacyPolicyPath: true }, }); const bankCards = await prisma.bankCard.findMany({ where: { customerId: id }, select: { documentPath: true }, }); const identityDocs = await prisma.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.customer.delete({ where: { id }, }); } // Address operations export async function getCustomerAddresses(customerId: number) { return prisma.address.findMany({ where: { customerId }, orderBy: [{ isDefault: 'desc' }, { createdAt: 'desc' }], }); } export async function createAddress( customerId: number, data: { type: 'DELIVERY_RESIDENCE' | 'BILLING'; street: string; houseNumber: string; postalCode: string; city: string; country?: string; isDefault?: boolean; } ) { // If this is set as default, unset other defaults of same type if (data.isDefault) { await prisma.address.updateMany({ where: { customerId, type: data.type }, data: { isDefault: false }, }); } return prisma.address.create({ data: { customerId, ...data, }, }); } export async function updateAddress( id: number, data: { type?: 'DELIVERY_RESIDENCE' | 'BILLING'; street?: string; houseNumber?: string; postalCode?: string; city?: string; country?: string; isDefault?: boolean; } ) { const address = await prisma.address.findUnique({ where: { id } }); if (!address) throw new Error('Adresse nicht gefunden'); if (data.isDefault) { await prisma.address.updateMany({ where: { customerId: address.customerId, type: data.type || address.type, id: { not: id }, }, data: { isDefault: false }, }); } return prisma.address.update({ where: { id }, data, }); } export async function deleteAddress(id: number) { return prisma.address.delete({ where: { id } }); } // Bank card operations export async function getCustomerBankCards( customerId: number, showInactive: boolean = false ) { const where: Record = { customerId }; if (!showInactive) { where.isActive = true; } return prisma.bankCard.findMany({ where, orderBy: [{ isActive: 'desc' }, { createdAt: 'desc' }], }); } export async function createBankCard( customerId: number, data: { accountHolder: string; iban: string; bic?: string; bankName?: string; expiryDate?: Date; } ) { return prisma.bankCard.create({ data: { customerId, ...data, isActive: true, }, }); } export async function updateBankCard( id: number, data: { accountHolder?: string; iban?: string; bic?: string; bankName?: string; expiryDate?: Date; isActive?: boolean; } ) { return prisma.bankCard.update({ where: { id }, data, }); } export async function deleteBankCard(id: number) { // Erst Datei-Pfad holen, dann Datei löschen, dann DB-Eintrag löschen const bankCard = await prisma.bankCard.findUnique({ where: { id } }); if (bankCard?.documentPath) { deleteFileIfExists(bankCard.documentPath); } return prisma.bankCard.delete({ where: { id } }); } // Identity document operations export async function getCustomerDocuments( customerId: number, showInactive: boolean = false ) { const where: Record = { customerId }; if (!showInactive) { where.isActive = true; } return prisma.identityDocument.findMany({ where, orderBy: [{ isActive: 'desc' }, { createdAt: 'desc' }], }); } export async function createDocument( customerId: number, data: { type: 'ID_CARD' | 'PASSPORT' | 'DRIVERS_LICENSE' | 'OTHER'; documentNumber: string; issuingAuthority?: string; issueDate?: Date; expiryDate?: Date; licenseClasses?: string; licenseIssueDate?: Date; } ) { return prisma.identityDocument.create({ data: { customerId, ...data, isActive: true, }, }); } export async function updateDocument( id: number, data: { type?: 'ID_CARD' | 'PASSPORT' | 'DRIVERS_LICENSE' | 'OTHER'; documentNumber?: string; issuingAuthority?: string; issueDate?: Date; expiryDate?: Date; licenseClasses?: string; licenseIssueDate?: Date; isActive?: boolean; } ) { return prisma.identityDocument.update({ where: { id }, data, }); } export async function deleteDocument(id: number) { // Erst Datei-Pfad holen, dann Datei löschen, dann DB-Eintrag löschen const document = await prisma.identityDocument.findUnique({ where: { id } }); if (document?.documentPath) { deleteFileIfExists(document.documentPath); } return prisma.identityDocument.delete({ where: { id } }); } // Meter operations export async function getCustomerMeters( customerId: number, showInactive: boolean = false ) { const where: Record = { customerId }; if (!showInactive) { where.isActive = true; } return prisma.meter.findMany({ where, include: { address: true, readings: { orderBy: { readingDate: 'desc' }, take: 5, }, }, orderBy: [{ isActive: 'desc' }, { createdAt: 'desc' }], }); } // Schreibt den Endstand des Vorgänger-Zählers beim Zählerwechsel als // MeterReading. Wird beim Folgezähler-Anlegen aufgerufen (sowohl aus der // Kundenakte als auch aus der Vertragsansicht). Idempotent: existiert am // Wechseltag schon ein Reading, wird nichts angelegt. Validierung // monoton-steigend wird durchgereicht – wirft bei Konflikt. export async function recordPredecessorFinalReading( predecessorMeterId: number, switchAt: Date, value: number, ) { const meter = await prisma.meter.findUnique({ where: { id: predecessorMeterId }, select: { type: true }, }); if (!meter) return; const dayStart = new Date(switchAt); dayStart.setHours(0, 0, 0, 0); const dayEnd = new Date(dayStart); dayEnd.setDate(dayEnd.getDate() + 1); const existingSameDay = await prisma.meterReading.findFirst({ where: { meterId: predecessorMeterId, readingDate: { gte: dayStart, lt: dayEnd } }, }); if (existingSameDay) return; await validateReadingValue(predecessorMeterId, switchAt, value, undefined, 'HT'); await prisma.meterReading.create({ data: { meterId: predecessorMeterId, readingDate: switchAt, value, unit: meter.type === 'GAS' ? 'm³' : 'kWh', notes: 'Endstand bei Zählerwechsel (automatisch beim Folgezähler-Anlegen erfasst)', }, }); } // Lieferadresse muss zum Kunden gehören und vom Typ DELIVERY_RESIDENCE sein. // Wirft eine sprechende Fehlermeldung, die der Controller dem User durchreicht. async function assertDeliveryAddressBelongsToCustomer(addressId: number, customerId: number) { const addr = await prisma.address.findUnique({ where: { id: addressId } }); if (!addr || addr.customerId !== customerId) { throw new Error('Ungültige Lieferadresse'); } if (addr.type !== 'DELIVERY_RESIDENCE') { throw new Error('Nur Lieferadressen können einem Zähler zugeordnet werden'); } } export async function createMeter( customerId: number, data: { meterNumber: string; type: 'ELECTRICITY' | 'GAS'; tariffModel?: 'SINGLE' | 'DUAL'; location?: string; addressId?: number | null; // Optional: dieser Zähler ersetzt einen bestehenden (Folgezähler). // Beim Create werden alle Verträge, die den Vorgänger als aktuellen // Zähler nutzen, automatisch auf den neuen Zähler umgestellt // (ContractMeter-Eintrag analog zu Vertragsansicht). successorOf?: { predecessorMeterId: number; installedAt?: string; finalReadingPrevious?: number; // Default true im UI: alter Zähler wird nach dem Wechsel auf // isActive=false gesetzt. Kann ausgeschaltet werden, wenn der alte // Zähler aus irgendeinem Grund noch aktiv bleiben soll. deactivatePredecessor?: boolean; }; } ) { if (data.addressId == null) { throw new Error('Lieferadresse ist erforderlich'); } await assertDeliveryAddressBelongsToCustomer(data.addressId, customerId); // Vorgänger validieren (wenn Folgezähler) let predecessor: { id: number; customerId: number; type: 'ELECTRICITY' | 'GAS' } | null = null; if (data.successorOf) { const pred = await prisma.meter.findUnique({ where: { id: data.successorOf.predecessorMeterId }, select: { id: true, customerId: true, type: true }, }); if (!pred || pred.customerId !== customerId) { throw new Error('Vorgänger-Zähler nicht gefunden'); } if (pred.type !== data.type) { throw new Error('Vorgänger-Zähler muss denselben Typ haben (Strom/Gas)'); } predecessor = pred; // Endstand bereits hier validieren, damit kein verwaister Meter entsteht // wenn der Wert mit bestehenden Zählerständen kollidiert. if (data.successorOf.finalReadingPrevious != null) { const switchAt = data.successorOf.installedAt ? new Date(data.successorOf.installedAt) : new Date(); await validateReadingValue( pred.id, switchAt, data.successorOf.finalReadingPrevious, undefined, 'HT', ); } } const created = await prisma.meter.create({ data: { customerId, meterNumber: data.meterNumber, type: data.type, tariffModel: data.tariffModel, location: data.location, addressId: data.addressId, isActive: true, predecessorMeterId: predecessor?.id, }, include: { address: true, predecessor: true }, }); // Folgezähler-Propagation: alle Verträge, die den Vorgänger als aktuellen // Zähler nutzen, bekommen den neuen Zähler als Nachfolger angehängt // (analog zu addSuccessorMeter im contract.controller). if (predecessor && data.successorOf) { const installedAt = data.successorOf.installedAt ? new Date(data.successorOf.installedAt) : new Date(); const finalReading = data.successorOf.finalReadingPrevious; const affectedContracts = await prisma.energyContractDetails.findMany({ where: { meterId: predecessor.id }, include: { contractMeters: { orderBy: { position: 'asc' } } }, }); for (const ecd of affectedContracts) { // Vorhandenen ContractMeter für den Vorgänger als gewechselt markieren. // Falls noch kein ContractMeter für den Vorgänger existiert (Single-Meter- // Vertrag vor Multi-Meter-Refactor), legen wir ihn als position 0 an, // damit die Kette lückenlos ist. let predCM = ecd.contractMeters.find((cm) => cm.meterId === predecessor!.id); if (!predCM) { predCM = await prisma.contractMeter.create({ data: { energyContractDetailsId: ecd.id, meterId: predecessor.id, position: 0, installedAt: null, }, }); ecd.contractMeters.push(predCM); } await prisma.contractMeter.update({ where: { id: predCM.id }, data: { removedAt: installedAt, finalReading: finalReading != null ? finalReading : predCM.finalReading, }, }); const nextPosition = ecd.contractMeters.length > 0 ? Math.max(...ecd.contractMeters.map((cm) => cm.position)) + 1 : 0; // Idempotenz: falls (durch Doppel-Klick o.ä.) schon ein ContractMeter // mit dem neuen Zähler existiert, nicht doppelt anlegen. const existsForNew = await prisma.contractMeter.findUnique({ where: { energyContractDetailsId_meterId: { energyContractDetailsId: ecd.id, meterId: created.id, }, }, }); if (!existsForNew) { await prisma.contractMeter.create({ data: { energyContractDetailsId: ecd.id, meterId: created.id, position: nextPosition, installedAt, }, }); } // Aktuellen Zähler am Vertrag aktualisieren await prisma.energyContractDetails.update({ where: { id: ecd.id }, data: { meterId: created.id }, }); } // Endstand des Vorgängers als regulären Zählerstand erfassen, damit er // in die Verbrauchsberechnung einfließt und in der Zählerstände-Liste // sichtbar ist. Idempotent gegen Doppel-Submit. if (data.successorOf.finalReadingPrevious != null) { await recordPredecessorFinalReading( predecessor.id, installedAt, data.successorOf.finalReadingPrevious, ); } // Alten Zähler deaktivieren (Default), sofern der Aufrufer das nicht // explizit auf false setzt. Macht den typischen Zählerwechsel-Workflow // ein-klick-fähig. if (data.successorOf.deactivatePredecessor !== false) { await prisma.meter.update({ where: { id: predecessor.id }, data: { isActive: false }, }); } } return created; } export async function updateMeter( id: number, data: { meterNumber?: string; type?: 'ELECTRICITY' | 'GAS'; tariffModel?: 'SINGLE' | 'DUAL'; location?: string; isActive?: boolean; addressId?: number | null; } ) { if (data.addressId !== undefined && data.addressId !== null) { const meter = await prisma.meter.findUnique({ where: { id }, select: { customerId: true } }); if (!meter) throw new Error('Zähler nicht gefunden'); await assertDeliveryAddressBelongsToCustomer(data.addressId, meter.customerId); } return prisma.meter.update({ where: { id }, data, include: { address: true }, }); } export async function deleteMeter(id: number) { // Prüfen ob der Zähler noch an Verträgen hängt const linkedContracts = await prisma.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.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.meter.delete({ where: { id } }); } export async function addMeterReading( meterId: number, data: { readingDate: Date; value: number; valueNt?: number; unit?: string; notes?: string; } ) { // 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.meterReading.create({ data: { meterId, readingDate: data.readingDate, value: data.value, valueNt: data.valueNt, unit: data.unit, notes: data.notes, }, }); } export async function getMeterReadings(meterId: number) { return prisma.meterReading.findMany({ where: { meterId }, orderBy: { readingDate: 'desc' }, }); } export async function updateMeterReading( meterId: number, readingId: number, data: { readingDate?: Date; value?: number; valueNt?: number | null; unit?: string; notes?: string; } ) { // Verify the reading belongs to the meter const reading = await prisma.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.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: number, readingDate: Date, value: number, excludeReadingId?: number, tariffLabel: 'HT' | 'NT' = 'HT') { const existing = await prisma.meterReading.findMany({ where: { meterId, ...(excludeReadingId ? { id: { not: excludeReadingId } } : {}) }, orderBy: { readingDate: 'asc' }, }); const fmtDate = (d: Date) => d.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' }); const fmtVal = (v: number) => v.toLocaleString('de-DE'); const label = tariffLabel === 'NT' ? 'NT-Zählerstand' : 'Zählerstand'; // Vergleichswert aus bestehendem Reading extrahieren const getVal = (r: typeof existing[0]) => 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))})`); } } export async function deleteMeterReading(meterId: number, readingId: number) { // Verify the reading belongs to the meter const reading = await prisma.meterReading.findFirst({ where: { id: readingId, meterId }, }); if (!reading) { throw new Error('Zählerstand nicht gefunden'); } return prisma.meterReading.delete({ where: { id: readingId }, }); } // ==================== PORTAL SETTINGS ==================== export async function updatePortalSettings( customerId: number, data: { portalEnabled?: boolean; portalEmail?: string | null; } ) { // Wenn Portal deaktiviert wird, Passwort-Hash nicht löschen (für spätere Reaktivierung) return prisma.customer.update({ where: { id: customerId }, data: { portalEnabled: data.portalEnabled, portalEmail: data.portalEmail, }, select: { id: true, portalEnabled: true, portalEmail: true, portalLastLogin: true, }, }); } export async function getPortalSettings(customerId: number) { return prisma.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 ==================== export async function getCustomerRepresentatives(customerId: number) { // Holt alle Kunden, die der angegebene Kunde vertreten kann (dieser ist der Vertreter) return prisma.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' }, }); } export async function getRepresentedByList(customerId: number) { // Holt alle Kunden, die den angegebenen Kunden vertreten können return prisma.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' }, }); } export async function addRepresentative( customerId: number, // Der Kunde, dessen Verträge eingesehen werden dürfen representativeId: number, // Der Kunde, der einsehen darf notes?: string ) { // Prüfen, ob beide Kunden existieren const [customer, representative] = await Promise.all([ prisma.customer.findUnique({ where: { id: customerId } }), prisma.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.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, }, }, }, }); } export async function removeRepresentative(customerId: number, representativeId: number) { // Anstatt zu löschen, setzen wir isActive auf false return prisma.customerRepresentative.update({ where: { customerId_representativeId: { customerId, representativeId }, }, data: { isActive: false }, }); } export async function searchCustomersForRepresentative(search: string, excludeCustomerId: number) { // Sucht Kunden, die als Vertreter hinzugefügt werden können // Nur Kunden mit aktiviertem Portal return prisma.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, }); }