1060 lines
44 KiB
JavaScript
1060 lines
44 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;
|
|
};
|
|
})();
|
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.getCustomers = getCustomers;
|
|
exports.getCustomer = getCustomer;
|
|
exports.createCustomer = createCustomer;
|
|
exports.updateCustomer = updateCustomer;
|
|
exports.deleteCustomer = deleteCustomer;
|
|
exports.getAddresses = getAddresses;
|
|
exports.createAddress = createAddress;
|
|
exports.updateAddress = updateAddress;
|
|
exports.deleteAddress = deleteAddress;
|
|
exports.getBankCards = getBankCards;
|
|
exports.createBankCard = createBankCard;
|
|
exports.updateBankCard = updateBankCard;
|
|
exports.deleteBankCard = deleteBankCard;
|
|
exports.getDocuments = getDocuments;
|
|
exports.createDocument = createDocument;
|
|
exports.updateDocument = updateDocument;
|
|
exports.deleteDocument = deleteDocument;
|
|
exports.getMeters = getMeters;
|
|
exports.createMeter = createMeter;
|
|
exports.updateMeter = updateMeter;
|
|
exports.deleteMeter = deleteMeter;
|
|
exports.getMeterReadings = getMeterReadings;
|
|
exports.addMeterReading = addMeterReading;
|
|
exports.updateMeterReading = updateMeterReading;
|
|
exports.deleteMeterReading = deleteMeterReading;
|
|
exports.reportMeterReading = reportMeterReading;
|
|
exports.getMyMeters = getMyMeters;
|
|
exports.markReadingTransferred = markReadingTransferred;
|
|
exports.getPortalSettings = getPortalSettings;
|
|
exports.updatePortalSettings = updatePortalSettings;
|
|
exports.setPortalPassword = setPortalPassword;
|
|
exports.getPortalPassword = getPortalPassword;
|
|
exports.getRepresentatives = getRepresentatives;
|
|
exports.addRepresentative = addRepresentative;
|
|
exports.removeRepresentative = removeRepresentative;
|
|
exports.searchForRepresentative = searchForRepresentative;
|
|
const prisma_js_1 = __importDefault(require("../lib/prisma.js"));
|
|
const customerService = __importStar(require("../services/customer.service.js"));
|
|
const authService = __importStar(require("../services/auth.service.js"));
|
|
const audit_service_js_1 = require("../services/audit.service.js");
|
|
// Customer CRUD
|
|
async function getCustomers(req, res) {
|
|
try {
|
|
const { search, type, page, limit } = req.query;
|
|
const result = await customerService.getAllCustomers({
|
|
search: search,
|
|
type: type,
|
|
page: page ? parseInt(page) : undefined,
|
|
limit: limit ? parseInt(limit) : undefined,
|
|
});
|
|
res.json({ success: true, data: result.customers, pagination: result.pagination });
|
|
}
|
|
catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Fehler beim Laden der Kunden',
|
|
});
|
|
}
|
|
}
|
|
async function getCustomer(req, res) {
|
|
try {
|
|
const customer = await customerService.getCustomerById(parseInt(req.params.id));
|
|
if (!customer) {
|
|
res.status(404).json({ success: false, error: 'Kunde nicht gefunden' });
|
|
return;
|
|
}
|
|
res.json({ success: true, data: customer });
|
|
}
|
|
catch (error) {
|
|
res.status(500).json({ success: false, error: 'Fehler beim Laden des Kunden' });
|
|
}
|
|
}
|
|
async function createCustomer(req, res) {
|
|
try {
|
|
const data = { ...req.body };
|
|
// Convert birthDate string to Date if present
|
|
if (data.birthDate) {
|
|
data.birthDate = new Date(data.birthDate);
|
|
}
|
|
const customer = await customerService.createCustomer(data);
|
|
await (0, audit_service_js_1.logChange)({
|
|
req, action: 'CREATE', resourceType: 'Customer',
|
|
resourceId: customer.id.toString(),
|
|
label: `Kunde ${customer.customerNumber} angelegt (${customer.firstName} ${customer.lastName})`,
|
|
customerId: customer.id,
|
|
});
|
|
res.status(201).json({ success: true, data: customer });
|
|
}
|
|
catch (error) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Fehler beim Erstellen des Kunden',
|
|
});
|
|
}
|
|
}
|
|
async function updateCustomer(req, res) {
|
|
try {
|
|
const customerId = parseInt(req.params.id);
|
|
const data = { ...req.body };
|
|
// Vorherigen Stand laden für Audit
|
|
const before = await prisma_js_1.default.customer.findUnique({ where: { id: customerId } });
|
|
// Convert birthDate string to Date if present, empty string to null
|
|
if (data.birthDate === '' || data.birthDate === null) {
|
|
data.birthDate = null;
|
|
}
|
|
else if (data.birthDate) {
|
|
data.birthDate = new Date(data.birthDate);
|
|
}
|
|
// Leere Strings in optionalen Feldern zu null konvertieren
|
|
const nullableFields = ['salutation', 'birthPlace', 'phone', 'mobile', 'email', 'companyName', 'taxNumber', 'businessRegistration', 'commercialRegister', 'commercialRegisterNumber', 'notes'];
|
|
for (const field of nullableFields) {
|
|
if (data[field] === '')
|
|
data[field] = null;
|
|
}
|
|
const customer = await customerService.updateCustomer(customerId, data);
|
|
// Audit: Geänderte Felder ermitteln und loggen
|
|
if (before) {
|
|
const changes = {};
|
|
const fieldLabels = {
|
|
salutation: 'Anrede', firstName: 'Vorname', lastName: 'Nachname', email: 'E-Mail',
|
|
phone: 'Telefon', mobile: 'Mobil', birthDate: 'Geburtsdatum', birthPlace: 'Geburtsort',
|
|
companyName: 'Firma', type: 'Typ', taxNumber: 'Steuernummer', notes: 'Notizen',
|
|
};
|
|
for (const [key, value] of Object.entries(data)) {
|
|
// Technische/interne Felder überspringen
|
|
if (['id', 'createdAt', 'updatedAt', 'customerNumber', 'portalPasswordHash', 'portalPasswordEncrypted'].includes(key))
|
|
continue;
|
|
const oldVal = before[key];
|
|
const newVal = value;
|
|
// Normalisieren: null, undefined, "" werden alle als "leer" behandelt
|
|
const normalize = (v) => {
|
|
if (v === null || v === undefined || v === '')
|
|
return null;
|
|
if (v instanceof Date)
|
|
return v.toISOString().split('T')[0];
|
|
return v;
|
|
};
|
|
const oldNorm = normalize(oldVal);
|
|
const newNorm = normalize(newVal);
|
|
if (JSON.stringify(oldNorm) !== JSON.stringify(newNorm)) {
|
|
const label = fieldLabels[key] || key;
|
|
const formatVal = (v) => {
|
|
if (v === null || v === undefined || v === '')
|
|
return '-';
|
|
if (v instanceof Date)
|
|
return v.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' });
|
|
if (typeof v === 'boolean')
|
|
return v ? 'Ja' : 'Nein';
|
|
return String(v);
|
|
};
|
|
changes[label] = { von: formatVal(oldVal), nach: formatVal(newVal) };
|
|
}
|
|
}
|
|
if (Object.keys(changes).length > 0) {
|
|
const changeList = Object.entries(changes).map(([f, c]) => `${f}: ${c.von} → ${c.nach}`).join(', ');
|
|
await (0, audit_service_js_1.logChange)({
|
|
req, action: 'UPDATE', resourceType: 'Customer',
|
|
resourceId: customerId.toString(),
|
|
label: `Kunde ${before.customerNumber} aktualisiert: ${changeList}`,
|
|
details: changes,
|
|
customerId,
|
|
});
|
|
}
|
|
}
|
|
res.json({ success: true, data: customer });
|
|
}
|
|
catch (error) {
|
|
console.error('Update customer error:', error);
|
|
res.status(400).json({
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren des Kunden',
|
|
});
|
|
}
|
|
}
|
|
async function deleteCustomer(req, res) {
|
|
try {
|
|
const customerId = parseInt(req.params.id);
|
|
const customer = await prisma_js_1.default.customer.findUnique({ where: { id: customerId }, select: { customerNumber: true, firstName: true, lastName: true } });
|
|
await customerService.deleteCustomer(customerId);
|
|
await (0, audit_service_js_1.logChange)({
|
|
req, action: 'DELETE', resourceType: 'Customer',
|
|
resourceId: customerId.toString(),
|
|
label: `Kunde ${customer?.customerNumber} gelöscht (${customer?.firstName} ${customer?.lastName})`,
|
|
customerId,
|
|
});
|
|
res.json({ success: true, message: 'Kunde gelöscht' });
|
|
}
|
|
catch (error) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Fehler beim Löschen des Kunden',
|
|
});
|
|
}
|
|
}
|
|
// Addresses
|
|
async function getAddresses(req, res) {
|
|
try {
|
|
const addresses = await customerService.getCustomerAddresses(parseInt(req.params.customerId));
|
|
res.json({ success: true, data: addresses });
|
|
}
|
|
catch (error) {
|
|
res.status(500).json({ success: false, error: 'Fehler beim Laden der Adressen' });
|
|
}
|
|
}
|
|
async function createAddress(req, res) {
|
|
try {
|
|
const customerId = parseInt(req.params.customerId);
|
|
const address = await customerService.createAddress(customerId, req.body);
|
|
await (0, audit_service_js_1.logChange)({
|
|
req, action: 'CREATE', resourceType: 'Address',
|
|
resourceId: address.id.toString(),
|
|
label: `Adresse hinzugefügt für Kunde #${customerId}`,
|
|
customerId,
|
|
});
|
|
res.status(201).json({ success: true, data: address });
|
|
}
|
|
catch (error) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Fehler beim Erstellen der Adresse',
|
|
});
|
|
}
|
|
}
|
|
async function updateAddress(req, res) {
|
|
try {
|
|
const addressId = parseInt(req.params.id);
|
|
const data = req.body;
|
|
// Vorherigen Stand laden für Audit
|
|
const before = await prisma_js_1.default.address.findUnique({ where: { id: addressId } });
|
|
const address = await customerService.updateAddress(addressId, data);
|
|
const customerId = address.customerId;
|
|
// Audit: Geänderte Felder ermitteln und loggen
|
|
if (before) {
|
|
const changes = {};
|
|
const fieldLabels = {
|
|
street: 'Straße', houseNumber: 'Hausnummer', postalCode: 'PLZ',
|
|
city: 'Stadt', country: 'Land', type: 'Typ', isDefault: 'Standard',
|
|
};
|
|
for (const [key, newVal] of Object.entries(data)) {
|
|
if (['id', 'createdAt', 'updatedAt'].includes(key))
|
|
continue;
|
|
const oldVal = before[key];
|
|
const norm = (v) => (v === null || v === undefined || v === '' ? null : v);
|
|
if (JSON.stringify(norm(oldVal)) !== JSON.stringify(norm(newVal))) {
|
|
const label = fieldLabels[key] || key;
|
|
const formatVal = (v) => {
|
|
if (v === null || v === undefined || v === '')
|
|
return '-';
|
|
if (typeof v === 'boolean')
|
|
return v ? 'Ja' : 'Nein';
|
|
return String(v);
|
|
};
|
|
changes[label] = { von: formatVal(oldVal), nach: formatVal(newVal) };
|
|
}
|
|
}
|
|
const changeList = Object.entries(changes).map(([f, c]) => `${f}: ${c.von} → ${c.nach}`).join(', ');
|
|
await (0, audit_service_js_1.logChange)({
|
|
req, action: 'UPDATE', resourceType: 'Address',
|
|
resourceId: address.id.toString(),
|
|
label: changeList ? `Adresse aktualisiert für Kunde #${customerId}: ${changeList}` : `Adresse aktualisiert für Kunde #${customerId}`,
|
|
details: Object.keys(changes).length > 0 ? changes : undefined,
|
|
customerId,
|
|
});
|
|
}
|
|
else {
|
|
await (0, audit_service_js_1.logChange)({
|
|
req, action: 'UPDATE', resourceType: 'Address',
|
|
resourceId: address.id.toString(),
|
|
label: `Adresse aktualisiert für Kunde #${customerId}`,
|
|
customerId,
|
|
});
|
|
}
|
|
res.json({ success: true, data: address });
|
|
}
|
|
catch (error) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren der Adresse',
|
|
});
|
|
}
|
|
}
|
|
async function deleteAddress(req, res) {
|
|
try {
|
|
const addressId = parseInt(req.params.id);
|
|
const addr = await prisma_js_1.default.address.findUnique({ where: { id: addressId }, select: { customerId: true } });
|
|
const customerId = addr?.customerId;
|
|
await customerService.deleteAddress(addressId);
|
|
await (0, audit_service_js_1.logChange)({
|
|
req, action: 'DELETE', resourceType: 'Address',
|
|
resourceId: addressId.toString(),
|
|
label: `Adresse gelöscht für Kunde #${customerId}`,
|
|
customerId: customerId ?? undefined,
|
|
});
|
|
res.json({ success: true, message: 'Adresse gelöscht' });
|
|
}
|
|
catch (error) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Fehler beim Löschen der Adresse',
|
|
});
|
|
}
|
|
}
|
|
// Bank Cards
|
|
async function getBankCards(req, res) {
|
|
try {
|
|
const showInactive = req.query.showInactive === 'true';
|
|
const cards = await customerService.getCustomerBankCards(parseInt(req.params.customerId), showInactive);
|
|
res.json({ success: true, data: cards });
|
|
}
|
|
catch (error) {
|
|
res.status(500).json({ success: false, error: 'Fehler beim Laden der Bankkarten' });
|
|
}
|
|
}
|
|
async function createBankCard(req, res) {
|
|
try {
|
|
const customerId = parseInt(req.params.customerId);
|
|
const card = await customerService.createBankCard(customerId, req.body);
|
|
await (0, audit_service_js_1.logChange)({
|
|
req, action: 'CREATE', resourceType: 'BankCard',
|
|
resourceId: card.id.toString(),
|
|
label: `Bankverbindung hinzugefügt für Kunde #${customerId}`,
|
|
customerId,
|
|
});
|
|
res.status(201).json({ success: true, data: card });
|
|
}
|
|
catch (error) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Fehler beim Erstellen der Bankkarte',
|
|
});
|
|
}
|
|
}
|
|
async function updateBankCard(req, res) {
|
|
try {
|
|
const cardId = parseInt(req.params.id);
|
|
const data = req.body;
|
|
// Vorherigen Stand laden für Audit
|
|
const before = await prisma_js_1.default.bankCard.findUnique({ where: { id: cardId } });
|
|
const card = await customerService.updateBankCard(cardId, data);
|
|
const customerId = card.customerId;
|
|
// Audit: Geänderte Felder ermitteln und loggen
|
|
if (before) {
|
|
const changes = {};
|
|
const fieldLabels = {
|
|
iban: 'IBAN', bic: 'BIC', bankName: 'Bank',
|
|
accountHolder: 'Kontoinhaber', isActive: 'Aktiv',
|
|
};
|
|
for (const [key, newVal] of Object.entries(data)) {
|
|
if (['id', 'createdAt', 'updatedAt'].includes(key))
|
|
continue;
|
|
const oldVal = before[key];
|
|
const norm = (v) => (v === null || v === undefined || v === '' ? null : v);
|
|
if (JSON.stringify(norm(oldVal)) !== JSON.stringify(norm(newVal))) {
|
|
const label = fieldLabels[key] || key;
|
|
const formatVal = (v) => {
|
|
if (v === null || v === undefined || v === '')
|
|
return '-';
|
|
if (typeof v === 'boolean')
|
|
return v ? 'Ja' : 'Nein';
|
|
return String(v);
|
|
};
|
|
changes[label] = { von: formatVal(oldVal), nach: formatVal(newVal) };
|
|
}
|
|
}
|
|
const changeList = Object.entries(changes).map(([f, c]) => `${f}: ${c.von} → ${c.nach}`).join(', ');
|
|
await (0, audit_service_js_1.logChange)({
|
|
req, action: 'UPDATE', resourceType: 'BankCard',
|
|
resourceId: card.id.toString(),
|
|
label: changeList ? `Bankverbindung aktualisiert für Kunde #${customerId}: ${changeList}` : `Bankverbindung aktualisiert für Kunde #${customerId}`,
|
|
details: Object.keys(changes).length > 0 ? changes : undefined,
|
|
customerId,
|
|
});
|
|
}
|
|
else {
|
|
await (0, audit_service_js_1.logChange)({
|
|
req, action: 'UPDATE', resourceType: 'BankCard',
|
|
resourceId: card.id.toString(),
|
|
label: `Bankverbindung aktualisiert für Kunde #${customerId}`,
|
|
customerId,
|
|
});
|
|
}
|
|
res.json({ success: true, data: card });
|
|
}
|
|
catch (error) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren der Bankkarte',
|
|
});
|
|
}
|
|
}
|
|
async function deleteBankCard(req, res) {
|
|
try {
|
|
const cardId = parseInt(req.params.id);
|
|
const card = await prisma_js_1.default.bankCard.findUnique({ where: { id: cardId }, select: { customerId: true } });
|
|
const customerId = card?.customerId;
|
|
await customerService.deleteBankCard(cardId);
|
|
await (0, audit_service_js_1.logChange)({
|
|
req, action: 'DELETE', resourceType: 'BankCard',
|
|
resourceId: cardId.toString(),
|
|
label: `Bankverbindung gelöscht für Kunde #${customerId}`,
|
|
customerId: customerId ?? undefined,
|
|
});
|
|
res.json({ success: true, message: 'Bankkarte gelöscht' });
|
|
}
|
|
catch (error) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Fehler beim Löschen der Bankkarte',
|
|
});
|
|
}
|
|
}
|
|
// Identity Documents
|
|
async function getDocuments(req, res) {
|
|
try {
|
|
const showInactive = req.query.showInactive === 'true';
|
|
const docs = await customerService.getCustomerDocuments(parseInt(req.params.customerId), showInactive);
|
|
res.json({ success: true, data: docs });
|
|
}
|
|
catch (error) {
|
|
res.status(500).json({ success: false, error: 'Fehler beim Laden der Ausweise' });
|
|
}
|
|
}
|
|
async function createDocument(req, res) {
|
|
try {
|
|
const customerId = parseInt(req.params.customerId);
|
|
const doc = await customerService.createDocument(customerId, req.body);
|
|
await (0, audit_service_js_1.logChange)({
|
|
req, action: 'CREATE', resourceType: 'IdentityDocument',
|
|
resourceId: doc.id.toString(),
|
|
label: `Ausweis hinzugefügt für Kunde #${customerId}`,
|
|
customerId,
|
|
});
|
|
res.status(201).json({ success: true, data: doc });
|
|
}
|
|
catch (error) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Fehler beim Erstellen des Ausweises',
|
|
});
|
|
}
|
|
}
|
|
async function updateDocument(req, res) {
|
|
try {
|
|
const docId = parseInt(req.params.id);
|
|
const data = req.body;
|
|
// Vorherigen Stand laden für Audit
|
|
const before = await prisma_js_1.default.identityDocument.findUnique({ where: { id: docId } });
|
|
const doc = await customerService.updateDocument(docId, data);
|
|
const customerId = doc.customerId;
|
|
// Audit: Geänderte Felder ermitteln und loggen
|
|
if (before) {
|
|
const changes = {};
|
|
const fieldLabels = {
|
|
type: 'Dokumenttyp', documentNumber: 'Dokumentnummer',
|
|
issuingAuthority: 'Ausstellungsbehörde', issueDate: 'Ausstellungsdatum',
|
|
expiryDate: 'Ablaufdatum', isActive: 'Aktiv', licenseClasses: 'Führerscheinklassen',
|
|
};
|
|
for (const [key, newVal] of Object.entries(data)) {
|
|
if (['id', 'createdAt', 'updatedAt'].includes(key))
|
|
continue;
|
|
const oldVal = before[key];
|
|
const norm = (v) => {
|
|
if (v === null || v === undefined || v === '')
|
|
return null;
|
|
if (v instanceof Date)
|
|
return v.toISOString().split('T')[0];
|
|
return v;
|
|
};
|
|
if (JSON.stringify(norm(oldVal)) !== JSON.stringify(norm(newVal))) {
|
|
const label = fieldLabels[key] || key;
|
|
const formatVal = (v) => {
|
|
if (v === null || v === undefined || v === '')
|
|
return '-';
|
|
if (v instanceof Date)
|
|
return v.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' });
|
|
if (typeof v === 'boolean')
|
|
return v ? 'Ja' : 'Nein';
|
|
return String(v);
|
|
};
|
|
changes[label] = { von: formatVal(oldVal), nach: formatVal(newVal) };
|
|
}
|
|
}
|
|
const changeList = Object.entries(changes).map(([f, c]) => `${f}: ${c.von} → ${c.nach}`).join(', ');
|
|
await (0, audit_service_js_1.logChange)({
|
|
req, action: 'UPDATE', resourceType: 'IdentityDocument',
|
|
resourceId: doc.id.toString(),
|
|
label: changeList ? `Ausweis aktualisiert für Kunde #${customerId}: ${changeList}` : `Ausweis aktualisiert für Kunde #${customerId}`,
|
|
details: Object.keys(changes).length > 0 ? changes : undefined,
|
|
customerId,
|
|
});
|
|
}
|
|
else {
|
|
await (0, audit_service_js_1.logChange)({
|
|
req, action: 'UPDATE', resourceType: 'IdentityDocument',
|
|
resourceId: doc.id.toString(),
|
|
label: `Ausweis aktualisiert für Kunde #${customerId}`,
|
|
customerId,
|
|
});
|
|
}
|
|
res.json({ success: true, data: doc });
|
|
}
|
|
catch (error) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren des Ausweises',
|
|
});
|
|
}
|
|
}
|
|
async function deleteDocument(req, res) {
|
|
try {
|
|
const docId = parseInt(req.params.id);
|
|
const doc = await prisma_js_1.default.identityDocument.findUnique({ where: { id: docId }, select: { customerId: true } });
|
|
const customerId = doc?.customerId;
|
|
await customerService.deleteDocument(docId);
|
|
await (0, audit_service_js_1.logChange)({
|
|
req, action: 'DELETE', resourceType: 'IdentityDocument',
|
|
resourceId: docId.toString(),
|
|
label: `Ausweis gelöscht für Kunde #${customerId}`,
|
|
customerId: customerId ?? undefined,
|
|
});
|
|
res.json({ success: true, message: 'Ausweis gelöscht' });
|
|
}
|
|
catch (error) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Fehler beim Löschen des Ausweises',
|
|
});
|
|
}
|
|
}
|
|
// Meters
|
|
async function getMeters(req, res) {
|
|
try {
|
|
const showInactive = req.query.showInactive === 'true';
|
|
const meters = await customerService.getCustomerMeters(parseInt(req.params.customerId), showInactive);
|
|
res.json({ success: true, data: meters });
|
|
}
|
|
catch (error) {
|
|
res.status(500).json({ success: false, error: 'Fehler beim Laden der Zähler' });
|
|
}
|
|
}
|
|
async function createMeter(req, res) {
|
|
try {
|
|
const customerId = parseInt(req.params.customerId);
|
|
const meter = await customerService.createMeter(customerId, req.body);
|
|
await (0, audit_service_js_1.logChange)({
|
|
req, action: 'CREATE', resourceType: 'Meter',
|
|
resourceId: meter.id.toString(),
|
|
label: `Zähler angelegt für Kunde #${customerId}`,
|
|
customerId,
|
|
});
|
|
res.status(201).json({ success: true, data: meter });
|
|
}
|
|
catch (error) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Fehler beim Erstellen des Zählers',
|
|
});
|
|
}
|
|
}
|
|
async function updateMeter(req, res) {
|
|
try {
|
|
const meterId = parseInt(req.params.id);
|
|
const data = req.body;
|
|
// Vorherigen Stand laden für Audit
|
|
const before = await prisma_js_1.default.meter.findUnique({ where: { id: meterId } });
|
|
const meter = await customerService.updateMeter(meterId, data);
|
|
const customerId = meter.customerId;
|
|
// Audit: Geänderte Felder ermitteln und loggen
|
|
if (before) {
|
|
const changes = {};
|
|
const fieldLabels = {
|
|
meterNumber: 'Zählernummer', type: 'Typ', tariffModel: 'Tarifmodell',
|
|
location: 'Standort', isActive: 'Aktiv',
|
|
};
|
|
for (const [key, newVal] of Object.entries(data)) {
|
|
if (['id', 'createdAt', 'updatedAt'].includes(key))
|
|
continue;
|
|
const oldVal = before[key];
|
|
const norm = (v) => (v === null || v === undefined || v === '' ? null : v);
|
|
if (JSON.stringify(norm(oldVal)) !== JSON.stringify(norm(newVal))) {
|
|
const label = fieldLabels[key] || key;
|
|
const formatVal = (v) => {
|
|
if (v === null || v === undefined || v === '')
|
|
return '-';
|
|
if (typeof v === 'boolean')
|
|
return v ? 'Ja' : 'Nein';
|
|
return String(v);
|
|
};
|
|
changes[label] = { von: formatVal(oldVal), nach: formatVal(newVal) };
|
|
}
|
|
}
|
|
const changeList = Object.entries(changes).map(([f, c]) => `${f}: ${c.von} → ${c.nach}`).join(', ');
|
|
await (0, audit_service_js_1.logChange)({
|
|
req, action: 'UPDATE', resourceType: 'Meter',
|
|
resourceId: meter.id.toString(),
|
|
label: changeList ? `Zähler aktualisiert: ${changeList}` : `Zähler aktualisiert`,
|
|
details: Object.keys(changes).length > 0 ? changes : undefined,
|
|
customerId,
|
|
});
|
|
}
|
|
else {
|
|
await (0, audit_service_js_1.logChange)({
|
|
req, action: 'UPDATE', resourceType: 'Meter',
|
|
resourceId: meter.id.toString(),
|
|
label: `Zähler aktualisiert`,
|
|
});
|
|
}
|
|
res.json({ success: true, data: meter });
|
|
}
|
|
catch (error) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren des Zählers',
|
|
});
|
|
}
|
|
}
|
|
async function deleteMeter(req, res) {
|
|
try {
|
|
const meterId = parseInt(req.params.id);
|
|
await customerService.deleteMeter(meterId);
|
|
await (0, audit_service_js_1.logChange)({
|
|
req, action: 'DELETE', resourceType: 'Meter',
|
|
resourceId: meterId.toString(),
|
|
label: `Zähler gelöscht`,
|
|
});
|
|
res.json({ success: true, message: 'Zähler gelöscht' });
|
|
}
|
|
catch (error) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Fehler beim Löschen des Zählers',
|
|
});
|
|
}
|
|
}
|
|
// Meter Readings
|
|
async function getMeterReadings(req, res) {
|
|
try {
|
|
const readings = await customerService.getMeterReadings(parseInt(req.params.meterId));
|
|
res.json({ success: true, data: readings });
|
|
}
|
|
catch (error) {
|
|
res.status(500).json({ success: false, error: 'Fehler beim Laden der Zählerstände' });
|
|
}
|
|
}
|
|
async function addMeterReading(req, res) {
|
|
try {
|
|
const { readingDate, value, valueNt, unit, notes } = req.body;
|
|
const meterId = parseInt(req.params.meterId);
|
|
const reading = await customerService.addMeterReading(meterId, {
|
|
readingDate: new Date(readingDate),
|
|
value: parseFloat(value),
|
|
valueNt: valueNt !== undefined && valueNt !== null && valueNt !== '' ? parseFloat(valueNt) : undefined,
|
|
unit,
|
|
notes,
|
|
});
|
|
// Audit: Zählerstand mit Kontext loggen
|
|
const meter = await prisma_js_1.default.meter.findUnique({
|
|
where: { id: meterId },
|
|
select: { meterNumber: true, customer: { select: { id: true, firstName: true, lastName: true } } },
|
|
});
|
|
if (meter) {
|
|
const ntInfo = valueNt ? ` / NT: ${parseFloat(valueNt)}` : '';
|
|
await (0, audit_service_js_1.logChange)({
|
|
req, action: 'CREATE', resourceType: 'MeterReading',
|
|
label: `Zählerstand ${parseFloat(value)}${ntInfo} ${unit || 'kWh'} für Zähler ${meter.meterNumber} erfasst (${meter.customer.firstName} ${meter.customer.lastName})`,
|
|
details: { zähler: meter.meterNumber, stand: parseFloat(value), datum: readingDate },
|
|
customerId: meter.customer.id,
|
|
});
|
|
}
|
|
res.status(201).json({ success: true, data: reading });
|
|
}
|
|
catch (error) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Fehler beim Hinzufügen des Zählerstands',
|
|
});
|
|
}
|
|
}
|
|
async function updateMeterReading(req, res) {
|
|
try {
|
|
const { readingDate, value, valueNt, unit, notes } = req.body;
|
|
const updateData = {};
|
|
if (readingDate !== undefined)
|
|
updateData.readingDate = new Date(readingDate);
|
|
if (value !== undefined)
|
|
updateData.value = parseFloat(value);
|
|
if (valueNt !== undefined)
|
|
updateData.valueNt = valueNt !== null && valueNt !== '' ? parseFloat(valueNt) : null;
|
|
if (unit !== undefined)
|
|
updateData.unit = unit;
|
|
if (notes !== undefined)
|
|
updateData.notes = notes;
|
|
const reading = await customerService.updateMeterReading(parseInt(req.params.meterId), parseInt(req.params.readingId), updateData);
|
|
await (0, audit_service_js_1.logChange)({
|
|
req, action: 'UPDATE', resourceType: 'MeterReading',
|
|
resourceId: reading.id.toString(),
|
|
label: `Zählerstand aktualisiert`,
|
|
});
|
|
res.json({ success: true, data: reading });
|
|
}
|
|
catch (error) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren des Zählerstands',
|
|
});
|
|
}
|
|
}
|
|
async function deleteMeterReading(req, res) {
|
|
try {
|
|
const readingId = parseInt(req.params.readingId);
|
|
await customerService.deleteMeterReading(parseInt(req.params.meterId), readingId);
|
|
await (0, audit_service_js_1.logChange)({
|
|
req, action: 'DELETE', resourceType: 'MeterReading',
|
|
resourceId: readingId.toString(),
|
|
label: `Zählerstand gelöscht`,
|
|
});
|
|
res.json({ success: true, data: null });
|
|
}
|
|
catch (error) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Fehler beim Löschen des Zählerstands',
|
|
});
|
|
}
|
|
}
|
|
// ==================== PORTAL: ZÄHLERSTAND MELDEN ====================
|
|
async function reportMeterReading(req, res) {
|
|
try {
|
|
const user = req.user;
|
|
if (!user?.isCustomerPortal || !user?.customerId) {
|
|
res.status(403).json({ success: false, error: 'Nur für Kundenportal-Benutzer' });
|
|
return;
|
|
}
|
|
const meterId = parseInt(req.params.meterId);
|
|
const { value, readingDate, notes } = req.body;
|
|
// Prüfe ob der Zähler zum Kunden gehört
|
|
const meter = await prisma_js_1.default.meter.findUnique({
|
|
where: { id: meterId },
|
|
select: { customerId: true },
|
|
});
|
|
if (!meter || meter.customerId !== user.customerId) {
|
|
res.status(403).json({ success: false, error: 'Kein Zugriff auf diesen Zähler' });
|
|
return;
|
|
}
|
|
const parsedDate = readingDate ? new Date(readingDate) : new Date();
|
|
const parsedValue = parseFloat(value);
|
|
// Validierung über den Service (monoton steigend)
|
|
const reading = await customerService.addMeterReading(meterId, {
|
|
readingDate: parsedDate,
|
|
value: parsedValue,
|
|
notes,
|
|
});
|
|
// Status auf REPORTED setzen
|
|
await prisma_js_1.default.meterReading.update({
|
|
where: { id: reading.id },
|
|
data: { reportedBy: user.email, status: 'REPORTED' },
|
|
});
|
|
// Audit
|
|
const meterInfo = await prisma_js_1.default.meter.findUnique({ where: { id: meterId }, select: { meterNumber: true } });
|
|
await (0, audit_service_js_1.logChange)({
|
|
req, action: 'CREATE', resourceType: 'MeterReading',
|
|
label: `Zählerstand ${parsedValue} gemeldet (Zähler ${meterInfo?.meterNumber || meterId})`,
|
|
details: { zähler: meterInfo?.meterNumber, stand: parsedValue, datum: parsedDate.toISOString() },
|
|
customerId: user.customerId,
|
|
});
|
|
res.status(201).json({ success: true, data: reading });
|
|
}
|
|
catch (error) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Fehler beim Melden des Zählerstands',
|
|
});
|
|
}
|
|
}
|
|
async function getMyMeters(req, res) {
|
|
try {
|
|
const user = req.user;
|
|
if (!user?.isCustomerPortal || !user?.customerId) {
|
|
res.status(403).json({ success: false, error: 'Nur für Kundenportal-Benutzer' });
|
|
return;
|
|
}
|
|
const meters = await prisma_js_1.default.meter.findMany({
|
|
where: { customerId: user.customerId, isActive: true },
|
|
include: {
|
|
readings: {
|
|
orderBy: { readingDate: 'desc' },
|
|
take: 5,
|
|
},
|
|
},
|
|
orderBy: { createdAt: 'asc' },
|
|
});
|
|
res.json({ success: true, data: meters });
|
|
}
|
|
catch (error) {
|
|
res.status(500).json({ success: false, error: 'Fehler beim Laden der Zähler' });
|
|
}
|
|
}
|
|
async function markReadingTransferred(req, res) {
|
|
try {
|
|
const meterId = parseInt(req.params.meterId);
|
|
const readingId = parseInt(req.params.readingId);
|
|
const reading = await prisma_js_1.default.meterReading.update({
|
|
where: { id: readingId },
|
|
data: {
|
|
status: 'TRANSFERRED',
|
|
transferredAt: new Date(),
|
|
transferredBy: req.user?.email,
|
|
},
|
|
});
|
|
await (0, audit_service_js_1.logChange)({
|
|
req, action: 'UPDATE', resourceType: 'MeterReading',
|
|
resourceId: readingId.toString(),
|
|
label: `Zählerstand als übertragen markiert`,
|
|
});
|
|
res.json({ success: true, data: reading });
|
|
}
|
|
catch (error) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren',
|
|
});
|
|
}
|
|
}
|
|
// ==================== PORTAL SETTINGS ====================
|
|
async function getPortalSettings(req, res) {
|
|
try {
|
|
const settings = await customerService.getPortalSettings(parseInt(req.params.customerId));
|
|
if (!settings) {
|
|
res.status(404).json({ success: false, error: 'Kunde nicht gefunden' });
|
|
return;
|
|
}
|
|
// Passwort-Hash nicht zurückgeben, nur ob ein Passwort gesetzt ist
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
id: settings.id,
|
|
portalEnabled: settings.portalEnabled,
|
|
portalEmail: settings.portalEmail,
|
|
portalLastLogin: settings.portalLastLogin,
|
|
hasPassword: !!settings.portalPasswordHash,
|
|
},
|
|
});
|
|
}
|
|
catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Fehler beim Laden der Portal-Einstellungen',
|
|
});
|
|
}
|
|
}
|
|
async function updatePortalSettings(req, res) {
|
|
try {
|
|
const customerId = parseInt(req.params.customerId);
|
|
const { portalEnabled, portalEmail } = req.body;
|
|
// Vorherigen Stand laden für Audit
|
|
const before = await prisma_js_1.default.customer.findUnique({
|
|
where: { id: customerId },
|
|
select: { portalEnabled: true, portalEmail: true },
|
|
});
|
|
const settings = await customerService.updatePortalSettings(customerId, {
|
|
portalEnabled,
|
|
portalEmail,
|
|
});
|
|
// Audit: Geänderte Felder ermitteln und loggen
|
|
const data = { portalEnabled, portalEmail };
|
|
if (before) {
|
|
const changes = {};
|
|
const fieldLabels = {
|
|
portalEnabled: 'Portal aktiv', portalEmail: 'Portal-E-Mail',
|
|
};
|
|
for (const [key, newVal] of Object.entries(data)) {
|
|
if (newVal === undefined)
|
|
continue;
|
|
const oldVal = before[key];
|
|
const norm = (v) => (v === null || v === undefined || v === '' ? null : v);
|
|
if (JSON.stringify(norm(oldVal)) !== JSON.stringify(norm(newVal))) {
|
|
const label = fieldLabels[key] || key;
|
|
const formatVal = (v) => {
|
|
if (v === null || v === undefined || v === '')
|
|
return '-';
|
|
if (typeof v === 'boolean')
|
|
return v ? 'Ja' : 'Nein';
|
|
return String(v);
|
|
};
|
|
changes[label] = { von: formatVal(oldVal), nach: formatVal(newVal) };
|
|
}
|
|
}
|
|
const changeList = Object.entries(changes).map(([f, c]) => `${f}: ${c.von} → ${c.nach}`).join(', ');
|
|
await (0, audit_service_js_1.logChange)({
|
|
req, action: 'UPDATE', resourceType: 'PortalSettings',
|
|
resourceId: customerId.toString(),
|
|
label: changeList ? `Portal-Einstellungen aktualisiert für Kunde #${customerId}: ${changeList}` : `Portal-Einstellungen aktualisiert für Kunde #${customerId}`,
|
|
details: Object.keys(changes).length > 0 ? changes : undefined,
|
|
customerId,
|
|
});
|
|
}
|
|
else {
|
|
await (0, audit_service_js_1.logChange)({
|
|
req, action: 'UPDATE', resourceType: 'PortalSettings',
|
|
resourceId: customerId.toString(),
|
|
label: `Portal-Einstellungen aktualisiert für Kunde #${customerId}`,
|
|
customerId,
|
|
});
|
|
}
|
|
res.json({ success: true, data: settings });
|
|
}
|
|
catch (error) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren der Portal-Einstellungen',
|
|
});
|
|
}
|
|
}
|
|
async function setPortalPassword(req, res) {
|
|
try {
|
|
const { password } = req.body;
|
|
if (!password || password.length < 6) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: 'Passwort muss mindestens 6 Zeichen lang sein',
|
|
});
|
|
return;
|
|
}
|
|
const customerId = parseInt(req.params.customerId);
|
|
await authService.setCustomerPortalPassword(customerId, password);
|
|
await (0, audit_service_js_1.logChange)({
|
|
req, action: 'UPDATE', resourceType: 'PortalSettings',
|
|
resourceId: customerId.toString(),
|
|
label: `Portal-Passwort gesetzt für Kunde #${customerId}`,
|
|
customerId,
|
|
});
|
|
res.json({ success: true, message: 'Passwort gesetzt' });
|
|
}
|
|
catch (error) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Fehler beim Setzen des Passworts',
|
|
});
|
|
}
|
|
}
|
|
async function getPortalPassword(req, res) {
|
|
try {
|
|
const password = await authService.getCustomerPortalPassword(parseInt(req.params.customerId));
|
|
res.json({ success: true, data: { password } });
|
|
}
|
|
catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Fehler beim Abrufen des Passworts',
|
|
});
|
|
}
|
|
}
|
|
// ==================== REPRESENTATIVE MANAGEMENT ====================
|
|
async function getRepresentatives(req, res) {
|
|
try {
|
|
// Wer kann diesen Kunden vertreten (representedBy)?
|
|
const representedBy = await customerService.getRepresentedByList(parseInt(req.params.customerId));
|
|
res.json({ success: true, data: representedBy });
|
|
}
|
|
catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Fehler beim Laden der Vertreter',
|
|
});
|
|
}
|
|
}
|
|
async function addRepresentative(req, res) {
|
|
try {
|
|
const customerId = parseInt(req.params.customerId);
|
|
const { representativeId, notes } = req.body;
|
|
const representative = await customerService.addRepresentative(customerId, parseInt(representativeId), notes);
|
|
await (0, audit_service_js_1.logChange)({
|
|
req, action: 'CREATE', resourceType: 'Representative',
|
|
resourceId: representative.id.toString(),
|
|
label: `Vertreter hinzugefügt für Kunde #${customerId}`,
|
|
customerId,
|
|
});
|
|
res.status(201).json({ success: true, data: representative });
|
|
}
|
|
catch (error) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Fehler beim Hinzufügen des Vertreters',
|
|
});
|
|
}
|
|
}
|
|
async function removeRepresentative(req, res) {
|
|
try {
|
|
const customerId = parseInt(req.params.customerId);
|
|
await customerService.removeRepresentative(customerId, parseInt(req.params.representativeId));
|
|
await (0, audit_service_js_1.logChange)({
|
|
req, action: 'DELETE', resourceType: 'Representative',
|
|
label: `Vertreter entfernt für Kunde #${customerId}`,
|
|
customerId,
|
|
});
|
|
res.json({ success: true, message: 'Vertreter entfernt' });
|
|
}
|
|
catch (error) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Fehler beim Entfernen des Vertreters',
|
|
});
|
|
}
|
|
}
|
|
async function searchForRepresentative(req, res) {
|
|
try {
|
|
const { search } = req.query;
|
|
if (!search || typeof search !== 'string' || search.length < 2) {
|
|
res.json({ success: true, data: [] });
|
|
return;
|
|
}
|
|
const customers = await customerService.searchCustomersForRepresentative(search, parseInt(req.params.customerId));
|
|
res.json({ success: true, data: customers });
|
|
}
|
|
catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Fehler bei der Suche',
|
|
});
|
|
}
|
|
}
|
|
//# sourceMappingURL=customer.controller.js.map
|