472 lines
20 KiB
JavaScript
472 lines
20 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.getContracts = getContracts;
|
|
exports.getContract = getContract;
|
|
exports.createContract = createContract;
|
|
exports.updateContract = updateContract;
|
|
exports.deleteContract = deleteContract;
|
|
exports.createFollowUp = createFollowUp;
|
|
exports.getContractPassword = getContractPassword;
|
|
exports.getSimCardCredentials = getSimCardCredentials;
|
|
exports.getInternetCredentials = getInternetCredentials;
|
|
exports.getSipCredentials = getSipCredentials;
|
|
exports.getCockpit = getCockpit;
|
|
exports.addSuccessorMeter = addSuccessorMeter;
|
|
exports.removeContractMeter = removeContractMeter;
|
|
exports.snoozeContract = snoozeContract;
|
|
const prisma_js_1 = __importDefault(require("../lib/prisma.js"));
|
|
const contractService = __importStar(require("../services/contract.service.js"));
|
|
const contractCockpitService = __importStar(require("../services/contractCockpit.service.js"));
|
|
const contractHistoryService = __importStar(require("../services/contractHistory.service.js"));
|
|
const authorizationService = __importStar(require("../services/authorization.service.js"));
|
|
const audit_service_js_1 = require("../services/audit.service.js");
|
|
async function getContracts(req, res) {
|
|
try {
|
|
const { customerId, type, status, search, page, limit, tree } = req.query;
|
|
// Baumstruktur für Kundenansicht
|
|
if (tree === 'true' && customerId) {
|
|
const treeData = await contractService.getContractTreeForCustomer(parseInt(customerId));
|
|
res.json({ success: true, data: treeData });
|
|
return;
|
|
}
|
|
// Für Kundenportal-Benutzer: nur eigene + vertretene Kunden MIT Vollmacht
|
|
let customerIds;
|
|
if (req.user?.isCustomerPortal && req.user.customerId) {
|
|
// Eigene Customer-ID immer
|
|
customerIds = [req.user.customerId];
|
|
// Vertretene Kunden nur wenn Vollmacht erteilt
|
|
const representedIds = req.user.representedCustomerIds || [];
|
|
for (const repCustId of representedIds) {
|
|
const hasAuth = await authorizationService.hasAuthorization(repCustId, req.user.customerId);
|
|
if (hasAuth) {
|
|
customerIds.push(repCustId);
|
|
}
|
|
}
|
|
}
|
|
const result = await contractService.getAllContracts({
|
|
customerId: customerId ? parseInt(customerId) : undefined,
|
|
customerIds, // Wird nur für Kundenportal-Benutzer gesetzt
|
|
type: type,
|
|
status: status,
|
|
search: search,
|
|
page: page ? parseInt(page) : undefined,
|
|
limit: limit ? parseInt(limit) : undefined,
|
|
});
|
|
res.json({
|
|
success: true,
|
|
data: result.contracts,
|
|
pagination: result.pagination,
|
|
});
|
|
}
|
|
catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Fehler beim Laden der Verträge',
|
|
});
|
|
}
|
|
}
|
|
async function getContract(req, res) {
|
|
try {
|
|
const contract = await contractService.getContractById(parseInt(req.params.id));
|
|
if (!contract) {
|
|
res.status(404).json({
|
|
success: false,
|
|
error: 'Vertrag nicht gefunden',
|
|
});
|
|
return;
|
|
}
|
|
// Für Kundenportal-Benutzer: Zugriff nur auf eigene + vertretene Kunden MIT Vollmacht
|
|
if (req.user?.isCustomerPortal && req.user.customerId) {
|
|
const allowedCustomerIds = [req.user.customerId];
|
|
const representedIds = req.user.representedCustomerIds || [];
|
|
for (const repCustId of representedIds) {
|
|
const hasAuth = await authorizationService.hasAuthorization(repCustId, req.user.customerId);
|
|
if (hasAuth) {
|
|
allowedCustomerIds.push(repCustId);
|
|
}
|
|
}
|
|
if (!allowedCustomerIds.includes(contract.customerId)) {
|
|
res.status(403).json({
|
|
success: false,
|
|
error: 'Kein Zugriff auf diesen Vertrag',
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
res.json({ success: true, data: contract });
|
|
}
|
|
catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Fehler beim Laden des Vertrags',
|
|
});
|
|
}
|
|
}
|
|
async function createContract(req, res) {
|
|
try {
|
|
const contract = await contractService.createContract(req.body);
|
|
await (0, audit_service_js_1.logChange)({
|
|
req, action: 'CREATE', resourceType: 'Contract',
|
|
resourceId: contract.id.toString(),
|
|
label: `Vertrag ${contract.contractNumber} angelegt`,
|
|
customerId: contract.customerId,
|
|
});
|
|
res.status(201).json({ success: true, data: contract });
|
|
}
|
|
catch (error) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Fehler beim Erstellen des Vertrags',
|
|
});
|
|
}
|
|
}
|
|
async function updateContract(req, res) {
|
|
try {
|
|
const contractId = parseInt(req.params.id);
|
|
// Vorherigen Stand laden für Audit-Vergleich
|
|
const before = await prisma_js_1.default.contract.findUnique({
|
|
where: { id: contractId },
|
|
include: { energyDetails: true, internetDetails: true, mobileDetails: true, tvDetails: true, carInsuranceDetails: true },
|
|
});
|
|
const contract = await contractService.updateContract(contractId, req.body);
|
|
// Geänderte Felder ermitteln
|
|
const changes = {};
|
|
const fieldLabels = {
|
|
status: 'Status', startDate: 'Vertragsbeginn', endDate: 'Vertragsende',
|
|
portalUsername: 'Portal-Benutzername', customerNumberAtProvider: 'Kundennummer beim Anbieter',
|
|
providerId: 'Anbieter', tariffId: 'Tarif', cancellationPeriodId: 'Kündigungsfrist',
|
|
contractDurationId: 'Vertragslaufzeit', platformId: 'Vertriebsplattform',
|
|
cancellationDate: 'Kündigungsdatum', cancellationSentDate: 'Kündigung gesendet am',
|
|
identityDocumentId: 'Ausweis', bankCardId: 'Bankverbindung', addressId: 'Adresse',
|
|
commission: 'Provision', notes: 'Notizen',
|
|
};
|
|
const energyLabels = {
|
|
meterId: 'Zähler', maloId: 'MaLo-ID', annualConsumption: 'Jahresverbrauch',
|
|
basePrice: 'Grundpreis', unitPrice: 'Arbeitspreis', unitPriceNt: 'NT-Arbeitspreis', bonus: 'Bonus',
|
|
};
|
|
// Hauptfelder vergleichen
|
|
const body = req.body;
|
|
if (before) {
|
|
for (const [key, newVal] of Object.entries(body)) {
|
|
if (['energyDetails', 'internetDetails', 'mobileDetails', 'tvDetails', 'carInsuranceDetails', 'password'].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;
|
|
changes[label] = { von: oldVal ?? '-', nach: newVal ?? '-' };
|
|
}
|
|
}
|
|
// Energie-Details vergleichen
|
|
if (body.energyDetails && before.energyDetails) {
|
|
for (const [key, newVal] of Object.entries(body.energyDetails)) {
|
|
const oldVal = before.energyDetails[key];
|
|
const norm = (v) => (v === null || v === undefined || v === '' ? null : v);
|
|
if (JSON.stringify(norm(oldVal)) !== JSON.stringify(norm(newVal))) {
|
|
const label = energyLabels[key] || key;
|
|
changes[label] = { von: oldVal ?? '-', nach: 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: 'Contract',
|
|
resourceId: contractId.toString(),
|
|
label: changeList
|
|
? `Vertrag ${before?.contractNumber || contractId} aktualisiert: ${changeList}`
|
|
: `Vertrag ${before?.contractNumber || contractId} aktualisiert`,
|
|
details: Object.keys(changes).length > 0 ? changes : undefined,
|
|
customerId: before?.customerId,
|
|
});
|
|
res.json({ success: true, data: contract });
|
|
}
|
|
catch (error) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren des Vertrags',
|
|
});
|
|
}
|
|
}
|
|
async function deleteContract(req, res) {
|
|
try {
|
|
const contractId = parseInt(req.params.id);
|
|
const contract = await prisma_js_1.default.contract.findUnique({ where: { id: contractId }, select: { contractNumber: true, customerId: true } });
|
|
await contractService.deleteContract(contractId);
|
|
await (0, audit_service_js_1.logChange)({
|
|
req, action: 'DELETE', resourceType: 'Contract',
|
|
resourceId: contractId.toString(),
|
|
label: `Vertrag ${contract?.contractNumber} gelöscht`,
|
|
customerId: contract?.customerId,
|
|
});
|
|
res.json({ success: true, message: 'Vertrag gelöscht' });
|
|
}
|
|
catch (error) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Fehler beim Löschen des Vertrags',
|
|
});
|
|
}
|
|
}
|
|
async function createFollowUp(req, res) {
|
|
try {
|
|
const previousContractId = parseInt(req.params.id);
|
|
// Vorgängervertrag laden für Vertragsnummer
|
|
const previousContract = await prisma_js_1.default.contract.findUnique({
|
|
where: { id: previousContractId },
|
|
select: { contractNumber: true },
|
|
});
|
|
if (!previousContract) {
|
|
res.status(404).json({ success: false, error: 'Vorgängervertrag nicht gefunden' });
|
|
return;
|
|
}
|
|
const contract = await contractService.createFollowUpContract(previousContractId);
|
|
const createdBy = req.user?.email || 'unbekannt';
|
|
// Historie-Eintrag für den Vorgängervertrag erstellen
|
|
await contractHistoryService.createFollowUpHistoryEntry(previousContractId, contract.contractNumber, createdBy);
|
|
// Historie-Eintrag für den neuen Folgevertrag erstellen
|
|
await contractHistoryService.createNewContractFromPredecessorEntry(contract.id, previousContract.contractNumber, createdBy);
|
|
await (0, audit_service_js_1.logChange)({
|
|
req, action: 'CREATE', resourceType: 'Contract',
|
|
resourceId: contract.id.toString(),
|
|
label: `Folgevertrag erstellt für ${previousContract.contractNumber}`,
|
|
customerId: contract.customerId,
|
|
});
|
|
res.status(201).json({ success: true, data: contract });
|
|
}
|
|
catch (error) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Fehler beim Erstellen des Folgevertrags',
|
|
});
|
|
}
|
|
}
|
|
async function getContractPassword(req, res) {
|
|
try {
|
|
const password = await contractService.getContractPassword(parseInt(req.params.id));
|
|
if (password === null) {
|
|
res.status(404).json({
|
|
success: false,
|
|
error: 'Kein Passwort hinterlegt',
|
|
});
|
|
return;
|
|
}
|
|
res.json({ success: true, data: { password } });
|
|
}
|
|
catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Fehler beim Entschlüsseln des Passworts',
|
|
});
|
|
}
|
|
}
|
|
async function getSimCardCredentials(req, res) {
|
|
try {
|
|
const credentials = await contractService.getSimCardCredentials(parseInt(req.params.simCardId));
|
|
res.json({ success: true, data: credentials });
|
|
}
|
|
catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Fehler beim Entschlüsseln der SIM-Karten-Daten',
|
|
});
|
|
}
|
|
}
|
|
async function getInternetCredentials(req, res) {
|
|
try {
|
|
const credentials = await contractService.getInternetCredentials(parseInt(req.params.id));
|
|
res.json({ success: true, data: credentials });
|
|
}
|
|
catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Fehler beim Entschlüsseln des Internet-Passworts',
|
|
});
|
|
}
|
|
}
|
|
async function getSipCredentials(req, res) {
|
|
try {
|
|
const credentials = await contractService.getSipCredentials(parseInt(req.params.phoneNumberId));
|
|
res.json({ success: true, data: credentials });
|
|
}
|
|
catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Fehler beim Entschlüsseln des SIP-Passworts',
|
|
});
|
|
}
|
|
}
|
|
// ==================== VERTRAGS-COCKPIT ====================
|
|
async function getCockpit(req, res) {
|
|
try {
|
|
const cockpitData = await contractCockpitService.getCockpitData();
|
|
res.json({ success: true, data: cockpitData });
|
|
}
|
|
catch (error) {
|
|
console.error('Cockpit error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Fehler beim Laden des Vertrags-Cockpits',
|
|
});
|
|
}
|
|
}
|
|
// ==================== FOLGEZÄHLER ====================
|
|
async function addSuccessorMeter(req, res) {
|
|
try {
|
|
const contractId = parseInt(req.params.id);
|
|
const { meterId, installedAt, finalReadingPrevious } = req.body;
|
|
const contract = await prisma_js_1.default.contract.findUnique({
|
|
where: { id: contractId },
|
|
include: { energyDetails: { include: { contractMeters: { orderBy: { position: 'asc' } } } } },
|
|
});
|
|
if (!contract?.energyDetails) {
|
|
res.status(404).json({ success: false, error: 'Energievertrag nicht gefunden' });
|
|
return;
|
|
}
|
|
const ecdId = contract.energyDetails.id;
|
|
const existingMeters = contract.energyDetails.contractMeters;
|
|
const nextPosition = existingMeters.length > 0
|
|
? Math.max(...existingMeters.map(m => m.position)) + 1
|
|
: 0;
|
|
// Vorherigen Zähler als gewechselt markieren
|
|
if (existingMeters.length > 0 && finalReadingPrevious !== undefined) {
|
|
const prevMeter = existingMeters[existingMeters.length - 1];
|
|
await prisma_js_1.default.contractMeter.update({
|
|
where: { id: prevMeter.id },
|
|
data: {
|
|
removedAt: installedAt ? new Date(installedAt) : new Date(),
|
|
finalReading: parseFloat(finalReadingPrevious),
|
|
},
|
|
});
|
|
}
|
|
const contractMeter = await prisma_js_1.default.contractMeter.create({
|
|
data: {
|
|
energyContractDetailsId: ecdId,
|
|
meterId: parseInt(meterId),
|
|
position: nextPosition,
|
|
installedAt: installedAt ? new Date(installedAt) : new Date(),
|
|
},
|
|
include: { meter: { include: { readings: true } } },
|
|
});
|
|
// Aktuellen Zähler am Vertrag aktualisieren
|
|
await prisma_js_1.default.energyContractDetails.update({
|
|
where: { id: ecdId },
|
|
data: { meterId: parseInt(meterId) },
|
|
});
|
|
await (0, audit_service_js_1.logChange)({
|
|
req, action: 'CREATE', resourceType: 'ContractMeter',
|
|
resourceId: contractMeter.id.toString(),
|
|
label: `Folgezähler hinzugefügt zu Vertrag #${contractId}`,
|
|
customerId: contract.customerId,
|
|
});
|
|
res.json({ success: true, data: contractMeter });
|
|
}
|
|
catch (error) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Fehler beim Hinzufügen des Folgezählers',
|
|
});
|
|
}
|
|
}
|
|
async function removeContractMeter(req, res) {
|
|
try {
|
|
const contractMeterId = parseInt(req.params.contractMeterId);
|
|
const contractId = parseInt(req.params.id);
|
|
await prisma_js_1.default.contractMeter.delete({ where: { id: contractMeterId } });
|
|
await (0, audit_service_js_1.logChange)({
|
|
req, action: 'DELETE', resourceType: 'ContractMeter',
|
|
resourceId: contractMeterId.toString(),
|
|
label: `Folgezähler entfernt von Vertrag #${contractId}`,
|
|
});
|
|
res.json({ success: true, data: null });
|
|
}
|
|
catch (error) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Fehler beim Entfernen',
|
|
});
|
|
}
|
|
}
|
|
// ==================== SNOOZE (VERTRAG ZURÜCKSTELLEN) ====================
|
|
async function snoozeContract(req, res) {
|
|
try {
|
|
const id = parseInt(req.params.id);
|
|
const { nextReviewDate, months } = req.body;
|
|
let reviewDate = null;
|
|
if (nextReviewDate) {
|
|
// Explizites Datum angegeben
|
|
reviewDate = new Date(nextReviewDate);
|
|
}
|
|
else if (months) {
|
|
// Monate angegeben → berechne Datum
|
|
reviewDate = new Date();
|
|
reviewDate.setMonth(reviewDate.getMonth() + months);
|
|
}
|
|
// Wenn beides leer → nextReviewDate wird auf null gesetzt (Snooze aufheben)
|
|
const updated = await prisma_js_1.default.contract.update({
|
|
where: { id },
|
|
data: { nextReviewDate: reviewDate },
|
|
select: {
|
|
id: true,
|
|
contractNumber: true,
|
|
nextReviewDate: true,
|
|
},
|
|
});
|
|
await (0, audit_service_js_1.logChange)({
|
|
req, action: 'UPDATE', resourceType: 'Contract',
|
|
resourceId: id.toString(),
|
|
label: `Vertrag ${updated.contractNumber} zurückgestellt`,
|
|
});
|
|
res.json({
|
|
success: true,
|
|
data: updated,
|
|
message: reviewDate
|
|
? `Vertrag zurückgestellt bis ${reviewDate.toLocaleDateString('de-DE')}`
|
|
: 'Zurückstellung aufgehoben',
|
|
});
|
|
}
|
|
catch (error) {
|
|
console.error('Snooze error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Fehler beim Zurückstellen des Vertrags',
|
|
});
|
|
}
|
|
}
|
|
//# sourceMappingURL=contract.controller.js.map
|