added place to telecommunication, added contract documents, added invoice to other contracts
This commit is contained in:
parent
eaa94e766a
commit
3dd4f7b656
|
|
@ -13,5 +13,8 @@ export declare function getSipCredentials(req: Request, res: Response): Promise<
|
||||||
export declare function getCockpit(req: AuthRequest, res: Response): Promise<void>;
|
export declare function getCockpit(req: AuthRequest, res: Response): Promise<void>;
|
||||||
export declare function addSuccessorMeter(req: AuthRequest, res: Response): Promise<void>;
|
export declare function addSuccessorMeter(req: AuthRequest, res: Response): Promise<void>;
|
||||||
export declare function removeContractMeter(req: AuthRequest, res: Response): Promise<void>;
|
export declare function removeContractMeter(req: AuthRequest, res: Response): Promise<void>;
|
||||||
|
export declare function getContractDocuments(req: AuthRequest, res: Response): Promise<void>;
|
||||||
|
export declare function uploadContractDocument(req: AuthRequest, res: Response): Promise<void>;
|
||||||
|
export declare function deleteContractDocument(req: AuthRequest, res: Response): Promise<void>;
|
||||||
export declare function snoozeContract(req: Request, res: Response): Promise<void>;
|
export declare function snoozeContract(req: Request, res: Response): Promise<void>;
|
||||||
//# sourceMappingURL=contract.controller.d.ts.map
|
//# sourceMappingURL=contract.controller.d.ts.map
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"version":3,"file":"contract.controller.d.ts","sourceRoot":"","sources":["../../src/controllers/contract.controller.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAM5C,OAAO,EAAe,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAG7D,wBAAsB,YAAY,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAgDjF;AAED,wBAAsB,WAAW,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAqChF;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB/E;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAsEnF;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAkB/E;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CA8CnF;AAED,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBpF;AAED,wBAAsB,qBAAqB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAUtF;AAED,wBAAsB,sBAAsB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAUvF;AAED,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAUlF;AAID,wBAAsB,UAAU,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAW/E;AAID,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CA+DtF;AAED,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBxF;AAID,wBAAsB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CA+C/E"}
|
{"version":3,"file":"contract.controller.d.ts","sourceRoot":"","sources":["../../src/controllers/contract.controller.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAM5C,OAAO,EAAe,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAG7D,wBAAsB,YAAY,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAgDjF;AAED,wBAAsB,WAAW,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAqChF;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB/E;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAsEnF;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAkB/E;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CA8CnF;AAED,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBpF;AAED,wBAAsB,qBAAqB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAUtF;AAED,wBAAsB,sBAAsB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAUvF;AAED,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAUlF;AAID,wBAAsB,UAAU,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAW/E;AAID,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CA+DtF;AAED,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBxF;AAID,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAWzF;AAED,wBAAsB,sBAAsB,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CA2C3F;AAED,wBAAsB,sBAAsB,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAqC3F;AAID,wBAAsB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CA+C/E"}
|
||||||
|
|
@ -49,6 +49,9 @@ exports.getSipCredentials = getSipCredentials;
|
||||||
exports.getCockpit = getCockpit;
|
exports.getCockpit = getCockpit;
|
||||||
exports.addSuccessorMeter = addSuccessorMeter;
|
exports.addSuccessorMeter = addSuccessorMeter;
|
||||||
exports.removeContractMeter = removeContractMeter;
|
exports.removeContractMeter = removeContractMeter;
|
||||||
|
exports.getContractDocuments = getContractDocuments;
|
||||||
|
exports.uploadContractDocument = uploadContractDocument;
|
||||||
|
exports.deleteContractDocument = deleteContractDocument;
|
||||||
exports.snoozeContract = snoozeContract;
|
exports.snoozeContract = snoozeContract;
|
||||||
const prisma_js_1 = __importDefault(require("../lib/prisma.js"));
|
const prisma_js_1 = __importDefault(require("../lib/prisma.js"));
|
||||||
const contractService = __importStar(require("../services/contract.service.js"));
|
const contractService = __importStar(require("../services/contract.service.js"));
|
||||||
|
|
@ -423,6 +426,94 @@ async function removeContractMeter(req, res) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// ==================== VERTRAGSDOKUMENTE ====================
|
||||||
|
async function getContractDocuments(req, res) {
|
||||||
|
try {
|
||||||
|
const contractId = parseInt(req.params.id);
|
||||||
|
const documents = await prisma_js_1.default.contractDocument.findMany({
|
||||||
|
where: { contractId },
|
||||||
|
orderBy: { createdAt: 'desc' },
|
||||||
|
});
|
||||||
|
res.json({ success: true, data: documents });
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
res.status(500).json({ success: false, error: 'Fehler beim Laden der Dokumente' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function uploadContractDocument(req, res) {
|
||||||
|
try {
|
||||||
|
const contractId = parseInt(req.params.id);
|
||||||
|
const { documentType, notes } = req.body;
|
||||||
|
if (!req.file) {
|
||||||
|
res.status(400).json({ success: false, error: 'Keine Datei hochgeladen' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!documentType) {
|
||||||
|
res.status(400).json({ success: false, error: 'Dokumenttyp erforderlich' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const documentPath = `/uploads/contract-documents/${req.file.filename}`;
|
||||||
|
const doc = await prisma_js_1.default.contractDocument.create({
|
||||||
|
data: {
|
||||||
|
contractId,
|
||||||
|
documentType,
|
||||||
|
documentPath,
|
||||||
|
originalName: req.file.originalname,
|
||||||
|
notes: notes || null,
|
||||||
|
uploadedBy: req.user?.email,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const contract = await prisma_js_1.default.contract.findUnique({ where: { id: contractId }, select: { contractNumber: true, customerId: true } });
|
||||||
|
await (0, audit_service_js_1.logChange)({
|
||||||
|
req, action: 'CREATE', resourceType: 'ContractDocument',
|
||||||
|
resourceId: doc.id.toString(),
|
||||||
|
label: `Dokument "${documentType}" hochgeladen für Vertrag ${contract?.contractNumber}`,
|
||||||
|
details: { typ: documentType, datei: req.file.originalname },
|
||||||
|
customerId: contract?.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 Hochladen',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function deleteContractDocument(req, res) {
|
||||||
|
try {
|
||||||
|
const documentId = parseInt(req.params.documentId);
|
||||||
|
const contractId = parseInt(req.params.id);
|
||||||
|
const doc = await prisma_js_1.default.contractDocument.findUnique({ where: { id: documentId } });
|
||||||
|
if (!doc || doc.contractId !== contractId) {
|
||||||
|
res.status(404).json({ success: false, error: 'Dokument nicht gefunden' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Datei löschen
|
||||||
|
const fs = await import('fs');
|
||||||
|
const path = await import('path');
|
||||||
|
const filePath = path.join(process.cwd(), doc.documentPath);
|
||||||
|
if (fs.existsSync(filePath)) {
|
||||||
|
fs.unlinkSync(filePath);
|
||||||
|
}
|
||||||
|
await prisma_js_1.default.contractDocument.delete({ where: { id: documentId } });
|
||||||
|
const contract = await prisma_js_1.default.contract.findUnique({ where: { id: contractId }, select: { contractNumber: true, customerId: true } });
|
||||||
|
await (0, audit_service_js_1.logChange)({
|
||||||
|
req, action: 'DELETE', resourceType: 'ContractDocument',
|
||||||
|
resourceId: documentId.toString(),
|
||||||
|
label: `Dokument "${doc.documentType}" gelöscht von Vertrag ${contract?.contractNumber}`,
|
||||||
|
details: { typ: doc.documentType, datei: doc.originalName },
|
||||||
|
customerId: contract?.customerId,
|
||||||
|
});
|
||||||
|
res.json({ success: true, message: 'Dokument gelöscht' });
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
error: error instanceof Error ? error.message : 'Fehler beim Löschen',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
// ==================== SNOOZE (VERTRAG ZURÜCKSTELLEN) ====================
|
// ==================== SNOOZE (VERTRAG ZURÜCKSTELLEN) ====================
|
||||||
async function snoozeContract(req, res) {
|
async function snoozeContract(req, res) {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -1 +1 @@
|
||||||
{"version":3,"file":"contract.routes.d.ts","sourceRoot":"","sources":["../../src/routes/contract.routes.ts"],"names":[],"mappings":"AAIA,QAAA,MAAM,MAAM,4CAAW,CAAC;AAkCxB,eAAe,MAAM,CAAC"}
|
{"version":3,"file":"contract.routes.d.ts","sourceRoot":"","sources":["../../src/routes/contract.routes.ts"],"names":[],"mappings":"AAQA,QAAA,MAAM,MAAM,4CAAW,CAAC;AAgExB,eAAe,MAAM,CAAC"}
|
||||||
|
|
@ -32,11 +32,40 @@ var __importStar = (this && this.__importStar) || (function () {
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||||
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||||
|
};
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
const express_1 = require("express");
|
const express_1 = require("express");
|
||||||
|
const multer_1 = __importDefault(require("multer"));
|
||||||
|
const path_1 = __importDefault(require("path"));
|
||||||
|
const fs_1 = __importDefault(require("fs"));
|
||||||
const contractController = __importStar(require("../controllers/contract.controller.js"));
|
const contractController = __importStar(require("../controllers/contract.controller.js"));
|
||||||
|
const invoiceController = __importStar(require("../controllers/invoice.controller.js"));
|
||||||
const auth_js_1 = require("../middleware/auth.js");
|
const auth_js_1 = require("../middleware/auth.js");
|
||||||
const router = (0, express_1.Router)();
|
const router = (0, express_1.Router)();
|
||||||
|
// Multer für Vertragsdokumente
|
||||||
|
const docUploadsDir = path_1.default.join(process.cwd(), 'uploads', 'contract-documents');
|
||||||
|
if (!fs_1.default.existsSync(docUploadsDir)) {
|
||||||
|
fs_1.default.mkdirSync(docUploadsDir, { recursive: true });
|
||||||
|
}
|
||||||
|
const docUpload = (0, multer_1.default)({
|
||||||
|
storage: multer_1.default.diskStorage({
|
||||||
|
destination: (_req, _file, cb) => cb(null, docUploadsDir),
|
||||||
|
filename: (_req, file, cb) => {
|
||||||
|
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
|
||||||
|
cb(null, `doc-${uniqueSuffix}${path_1.default.extname(file.originalname)}`);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
fileFilter: (_req, file, cb) => {
|
||||||
|
const allowed = ['application/pdf', 'image/jpeg', 'image/png', 'image/jpg'];
|
||||||
|
if (allowed.includes(file.mimetype))
|
||||||
|
cb(null, true);
|
||||||
|
else
|
||||||
|
cb(new Error('Nur PDF, JPG und PNG Dateien sind erlaubt'));
|
||||||
|
},
|
||||||
|
limits: { fileSize: 10 * 1024 * 1024 },
|
||||||
|
});
|
||||||
router.get('/', auth_js_1.authenticate, (0, auth_js_1.requirePermission)('contracts:read'), contractController.getContracts);
|
router.get('/', auth_js_1.authenticate, (0, auth_js_1.requirePermission)('contracts:read'), contractController.getContracts);
|
||||||
router.post('/', auth_js_1.authenticate, (0, auth_js_1.requirePermission)('contracts:create'), contractController.createContract);
|
router.post('/', auth_js_1.authenticate, (0, auth_js_1.requirePermission)('contracts:create'), contractController.createContract);
|
||||||
// Vertrags-Cockpit (muss VOR /:id stehen!)
|
// Vertrags-Cockpit (muss VOR /:id stehen!)
|
||||||
|
|
@ -48,6 +77,13 @@ router.delete('/:id', auth_js_1.authenticate, (0, auth_js_1.requirePermission)('
|
||||||
router.post('/:id/follow-up', auth_js_1.authenticate, (0, auth_js_1.requirePermission)('contracts:create'), contractController.createFollowUp);
|
router.post('/:id/follow-up', auth_js_1.authenticate, (0, auth_js_1.requirePermission)('contracts:create'), contractController.createFollowUp);
|
||||||
// Snooze (Vertrag zurückstellen)
|
// Snooze (Vertrag zurückstellen)
|
||||||
router.patch('/:id/snooze', auth_js_1.authenticate, (0, auth_js_1.requirePermission)('contracts:update'), contractController.snoozeContract);
|
router.patch('/:id/snooze', auth_js_1.authenticate, (0, auth_js_1.requirePermission)('contracts:update'), contractController.snoozeContract);
|
||||||
|
// Rechnungen (für alle Vertragstypen)
|
||||||
|
router.get('/:id/invoices', auth_js_1.authenticate, (0, auth_js_1.requirePermission)('contracts:read'), invoiceController.getInvoicesByContract);
|
||||||
|
router.post('/:id/invoices', auth_js_1.authenticate, (0, auth_js_1.requirePermission)('contracts:update'), invoiceController.addInvoiceByContract);
|
||||||
|
// Vertragsdokumente
|
||||||
|
router.get('/:id/documents', auth_js_1.authenticate, (0, auth_js_1.requirePermission)('contracts:read'), contractController.getContractDocuments);
|
||||||
|
router.post('/:id/documents', auth_js_1.authenticate, (0, auth_js_1.requirePermission)('contracts:update'), docUpload.single('file'), contractController.uploadContractDocument);
|
||||||
|
router.delete('/:id/documents/:documentId', auth_js_1.authenticate, (0, auth_js_1.requirePermission)('contracts:update'), contractController.deleteContractDocument);
|
||||||
// Folgezähler
|
// Folgezähler
|
||||||
router.post('/:id/successor-meter', auth_js_1.authenticate, (0, auth_js_1.requirePermission)('contracts:update'), contractController.addSuccessorMeter);
|
router.post('/:id/successor-meter', auth_js_1.authenticate, (0, auth_js_1.requirePermission)('contracts:update'), contractController.addSuccessorMeter);
|
||||||
router.delete('/:id/contract-meter/:contractMeterId', auth_js_1.authenticate, (0, auth_js_1.requirePermission)('contracts:update'), contractController.removeContractMeter);
|
router.delete('/:id/contract-meter/:contractMeterId', auth_js_1.authenticate, (0, auth_js_1.requirePermission)('contracts:update'), contractController.removeContractMeter);
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"version":3,"file":"contract.routes.js","sourceRoot":"","sources":["../../src/routes/contract.routes.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,qCAAiC;AACjC,0FAA4E;AAC5E,mDAAwE;AAExE,MAAM,MAAM,GAAG,IAAA,gBAAM,GAAE,CAAC;AAExB,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,sBAAY,EAAE,IAAA,2BAAiB,EAAC,gBAAgB,CAAC,EAAE,kBAAkB,CAAC,YAAY,CAAC,CAAC;AACpG,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,sBAAY,EAAE,IAAA,2BAAiB,EAAC,kBAAkB,CAAC,EAAE,kBAAkB,CAAC,cAAc,CAAC,CAAC;AAEzG,2CAA2C;AAC3C,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,sBAAY,EAAE,IAAA,2BAAiB,EAAC,gBAAgB,CAAC,EAAE,kBAAkB,CAAC,UAAU,CAAC,CAAC;AAEzG,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,sBAAY,EAAE,IAAA,2BAAiB,EAAC,gBAAgB,CAAC,EAAE,kBAAkB,CAAC,WAAW,CAAC,CAAC;AACtG,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,sBAAY,EAAE,IAAA,2BAAiB,EAAC,kBAAkB,CAAC,EAAE,kBAAkB,CAAC,cAAc,CAAC,CAAC;AAC3G,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,sBAAY,EAAE,IAAA,2BAAiB,EAAC,kBAAkB,CAAC,EAAE,kBAAkB,CAAC,cAAc,CAAC,CAAC;AAE9G,qBAAqB;AACrB,MAAM,CAAC,IAAI,CAAC,gBAAgB,EAAE,sBAAY,EAAE,IAAA,2BAAiB,EAAC,kBAAkB,CAAC,EAAE,kBAAkB,CAAC,cAAc,CAAC,CAAC;AAEtH,iCAAiC;AACjC,MAAM,CAAC,KAAK,CAAC,aAAa,EAAE,sBAAY,EAAE,IAAA,2BAAiB,EAAC,kBAAkB,CAAC,EAAE,kBAAkB,CAAC,cAAc,CAAC,CAAC;AAEpH,cAAc;AACd,MAAM,CAAC,IAAI,CAAC,sBAAsB,EAAE,sBAAY,EAAE,IAAA,2BAAiB,EAAC,kBAAkB,CAAC,EAAE,kBAAkB,CAAC,iBAAiB,CAAC,CAAC;AAC/H,MAAM,CAAC,MAAM,CAAC,sCAAsC,EAAE,sBAAY,EAAE,IAAA,2BAAiB,EAAC,kBAAkB,CAAC,EAAE,kBAAkB,CAAC,mBAAmB,CAAC,CAAC;AAEnJ,yBAAyB;AACzB,MAAM,CAAC,GAAG,CAAC,eAAe,EAAE,sBAAY,EAAE,IAAA,2BAAiB,EAAC,gBAAgB,CAAC,EAAE,kBAAkB,CAAC,mBAAmB,CAAC,CAAC;AAEvH,gCAAgC;AAChC,MAAM,CAAC,GAAG,CAAC,iCAAiC,EAAE,sBAAY,EAAE,IAAA,2BAAiB,EAAC,gBAAgB,CAAC,EAAE,kBAAkB,CAAC,qBAAqB,CAAC,CAAC;AAE3I,kCAAkC;AAClC,MAAM,CAAC,GAAG,CAAC,2BAA2B,EAAE,sBAAY,EAAE,IAAA,2BAAiB,EAAC,gBAAgB,CAAC,EAAE,kBAAkB,CAAC,sBAAsB,CAAC,CAAC;AAEtI,6BAA6B;AAC7B,MAAM,CAAC,GAAG,CAAC,6CAA6C,EAAE,sBAAY,EAAE,IAAA,2BAAiB,EAAC,gBAAgB,CAAC,EAAE,kBAAkB,CAAC,iBAAiB,CAAC,CAAC;AAEnJ,kBAAe,MAAM,CAAC"}
|
{"version":3,"file":"contract.routes.js","sourceRoot":"","sources":["../../src/routes/contract.routes.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,qCAAiC;AACjC,oDAA4B;AAC5B,gDAAwB;AACxB,4CAAoB;AACpB,0FAA4E;AAC5E,wFAA0E;AAC1E,mDAAwE;AAExE,MAAM,MAAM,GAAG,IAAA,gBAAM,GAAE,CAAC;AAExB,+BAA+B;AAC/B,MAAM,aAAa,GAAG,cAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,oBAAoB,CAAC,CAAC;AAChF,IAAI,CAAC,YAAE,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;IAClC,YAAE,CAAC,SAAS,CAAC,aAAa,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AACnD,CAAC;AACD,MAAM,SAAS,GAAG,IAAA,gBAAM,EAAC;IACvB,OAAO,EAAE,gBAAM,CAAC,WAAW,CAAC;QAC1B,WAAW,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,CAAC;QACzD,QAAQ,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE;YAC3B,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC;YACxE,EAAE,CAAC,IAAI,EAAE,OAAO,YAAY,GAAG,cAAI,CAAC,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;QACpE,CAAC;KACF,CAAC;IACF,UAAU,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE;QAC7B,MAAM,OAAO,GAAG,CAAC,iBAAiB,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,CAAC,CAAC;QAC5E,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC;YAAE,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;;YAC/C,EAAE,CAAC,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC,CAAC;IAClE,CAAC;IACD,MAAM,EAAE,EAAE,QAAQ,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE;CACvC,CAAC,CAAC;AAEH,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,sBAAY,EAAE,IAAA,2BAAiB,EAAC,gBAAgB,CAAC,EAAE,kBAAkB,CAAC,YAAY,CAAC,CAAC;AACpG,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,sBAAY,EAAE,IAAA,2BAAiB,EAAC,kBAAkB,CAAC,EAAE,kBAAkB,CAAC,cAAc,CAAC,CAAC;AAEzG,2CAA2C;AAC3C,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,sBAAY,EAAE,IAAA,2BAAiB,EAAC,gBAAgB,CAAC,EAAE,kBAAkB,CAAC,UAAU,CAAC,CAAC;AAEzG,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,sBAAY,EAAE,IAAA,2BAAiB,EAAC,gBAAgB,CAAC,EAAE,kBAAkB,CAAC,WAAW,CAAC,CAAC;AACtG,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,sBAAY,EAAE,IAAA,2BAAiB,EAAC,kBAAkB,CAAC,EAAE,kBAAkB,CAAC,cAAc,CAAC,CAAC;AAC3G,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,sBAAY,EAAE,IAAA,2BAAiB,EAAC,kBAAkB,CAAC,EAAE,kBAAkB,CAAC,cAAc,CAAC,CAAC;AAE9G,qBAAqB;AACrB,MAAM,CAAC,IAAI,CAAC,gBAAgB,EAAE,sBAAY,EAAE,IAAA,2BAAiB,EAAC,kBAAkB,CAAC,EAAE,kBAAkB,CAAC,cAAc,CAAC,CAAC;AAEtH,iCAAiC;AACjC,MAAM,CAAC,KAAK,CAAC,aAAa,EAAE,sBAAY,EAAE,IAAA,2BAAiB,EAAC,kBAAkB,CAAC,EAAE,kBAAkB,CAAC,cAAc,CAAC,CAAC;AAEpH,sCAAsC;AACtC,MAAM,CAAC,GAAG,CAAC,eAAe,EAAE,sBAAY,EAAE,IAAA,2BAAiB,EAAC,gBAAgB,CAAC,EAAE,iBAAiB,CAAC,qBAAqB,CAAC,CAAC;AACxH,MAAM,CAAC,IAAI,CAAC,eAAe,EAAE,sBAAY,EAAE,IAAA,2BAAiB,EAAC,kBAAkB,CAAC,EAAE,iBAAiB,CAAC,oBAAoB,CAAC,CAAC;AAE1H,oBAAoB;AACpB,MAAM,CAAC,GAAG,CAAC,gBAAgB,EAAE,sBAAY,EAAE,IAAA,2BAAiB,EAAC,gBAAgB,CAAC,EAAE,kBAAkB,CAAC,oBAAoB,CAAC,CAAC;AACzH,MAAM,CAAC,IAAI,CAAC,gBAAgB,EAAE,sBAAY,EAAE,IAAA,2BAAiB,EAAC,kBAAkB,CAAC,EAAE,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,kBAAkB,CAAC,sBAAsB,CAAC,CAAC;AACxJ,MAAM,CAAC,MAAM,CAAC,4BAA4B,EAAE,sBAAY,EAAE,IAAA,2BAAiB,EAAC,kBAAkB,CAAC,EAAE,kBAAkB,CAAC,sBAAsB,CAAC,CAAC;AAE5I,cAAc;AACd,MAAM,CAAC,IAAI,CAAC,sBAAsB,EAAE,sBAAY,EAAE,IAAA,2BAAiB,EAAC,kBAAkB,CAAC,EAAE,kBAAkB,CAAC,iBAAiB,CAAC,CAAC;AAC/H,MAAM,CAAC,MAAM,CAAC,sCAAsC,EAAE,sBAAY,EAAE,IAAA,2BAAiB,EAAC,kBAAkB,CAAC,EAAE,kBAAkB,CAAC,mBAAmB,CAAC,CAAC;AAEnJ,yBAAyB;AACzB,MAAM,CAAC,GAAG,CAAC,eAAe,EAAE,sBAAY,EAAE,IAAA,2BAAiB,EAAC,gBAAgB,CAAC,EAAE,kBAAkB,CAAC,mBAAmB,CAAC,CAAC;AAEvH,gCAAgC;AAChC,MAAM,CAAC,GAAG,CAAC,iCAAiC,EAAE,sBAAY,EAAE,IAAA,2BAAiB,EAAC,gBAAgB,CAAC,EAAE,kBAAkB,CAAC,qBAAqB,CAAC,CAAC;AAE3I,kCAAkC;AAClC,MAAM,CAAC,GAAG,CAAC,2BAA2B,EAAE,sBAAY,EAAE,IAAA,2BAAiB,EAAC,gBAAgB,CAAC,EAAE,kBAAkB,CAAC,sBAAsB,CAAC,CAAC;AAEtI,6BAA6B;AAC7B,MAAM,CAAC,GAAG,CAAC,6CAA6C,EAAE,sBAAY,EAAE,IAAA,2BAAiB,EAAC,gBAAgB,CAAC,EAAE,kBAAkB,CAAC,iBAAiB,CAAC,CAAC;AAEnJ,kBAAe,MAAM,CAAC"}
|
||||||
|
|
@ -297,7 +297,8 @@ export declare function getContractById(id: number, decryptPassword?: boolean):
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
notes: string | null;
|
notes: string | null;
|
||||||
documentPath: string | null;
|
documentPath: string | null;
|
||||||
energyContractDetailsId: number;
|
energyContractDetailsId: number | null;
|
||||||
|
contractId: number | null;
|
||||||
invoiceDate: Date;
|
invoiceDate: Date;
|
||||||
invoiceType: import(".prisma/client").$Enums.InvoiceType;
|
invoiceType: import(".prisma/client").$Enums.InvoiceType;
|
||||||
}[];
|
}[];
|
||||||
|
|
@ -426,7 +427,8 @@ export declare function getContractById(id: number, decryptPassword?: boolean):
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
notes: string | null;
|
notes: string | null;
|
||||||
documentPath: string | null;
|
documentPath: string | null;
|
||||||
energyContractDetailsId: number;
|
energyContractDetailsId: number | null;
|
||||||
|
contractId: number | null;
|
||||||
invoiceDate: Date;
|
invoiceDate: Date;
|
||||||
invoiceType: import(".prisma/client").$Enums.InvoiceType;
|
invoiceType: import(".prisma/client").$Enums.InvoiceType;
|
||||||
}[];
|
}[];
|
||||||
|
|
@ -466,6 +468,9 @@ export declare function getContractById(id: number, decryptPassword?: boolean):
|
||||||
routerModel: string | null;
|
routerModel: string | null;
|
||||||
installationDate: Date | null;
|
installationDate: Date | null;
|
||||||
internetUsername: string | null;
|
internetUsername: string | null;
|
||||||
|
propertyType: string | null;
|
||||||
|
propertyLocation: string | null;
|
||||||
|
connectionLocation: string | null;
|
||||||
}) | null;
|
}) | null;
|
||||||
mobileDetails: ({
|
mobileDetails: ({
|
||||||
simCards: {
|
simCards: {
|
||||||
|
|
@ -630,6 +635,9 @@ export declare function getContractById(id: number, decryptPassword?: boolean):
|
||||||
routerModel: string | null;
|
routerModel: string | null;
|
||||||
installationDate: Date | null;
|
installationDate: Date | null;
|
||||||
internetUsername: string | null;
|
internetUsername: string | null;
|
||||||
|
propertyType: string | null;
|
||||||
|
propertyLocation: string | null;
|
||||||
|
connectionLocation: string | null;
|
||||||
}) | null;
|
}) | null;
|
||||||
mobileDetails: ({
|
mobileDetails: ({
|
||||||
simCards: {
|
simCards: {
|
||||||
|
|
@ -679,6 +687,27 @@ export declare function getContractById(id: number, decryptPassword?: boolean):
|
||||||
deductibleFull: number | null;
|
deductibleFull: number | null;
|
||||||
previousInsurer: string | null;
|
previousInsurer: string | null;
|
||||||
} | null;
|
} | null;
|
||||||
|
documents: {
|
||||||
|
id: number;
|
||||||
|
createdAt: Date;
|
||||||
|
notes: string | null;
|
||||||
|
documentPath: string;
|
||||||
|
contractId: number;
|
||||||
|
documentType: string;
|
||||||
|
originalName: string;
|
||||||
|
uploadedBy: string | null;
|
||||||
|
}[];
|
||||||
|
invoices: {
|
||||||
|
id: number;
|
||||||
|
createdAt: Date;
|
||||||
|
updatedAt: Date;
|
||||||
|
notes: string | null;
|
||||||
|
documentPath: string | null;
|
||||||
|
energyContractDetailsId: number | null;
|
||||||
|
contractId: number | null;
|
||||||
|
invoiceDate: Date;
|
||||||
|
invoiceType: import(".prisma/client").$Enums.InvoiceType;
|
||||||
|
}[];
|
||||||
} & {
|
} & {
|
||||||
portalPasswordEncrypted: string | null;
|
portalPasswordEncrypted: string | null;
|
||||||
id: number;
|
id: number;
|
||||||
|
|
@ -772,6 +801,9 @@ interface ContractCreateData {
|
||||||
installationDate?: Date;
|
installationDate?: Date;
|
||||||
internetUsername?: string;
|
internetUsername?: string;
|
||||||
internetPassword?: string;
|
internetPassword?: string;
|
||||||
|
propertyType?: string;
|
||||||
|
propertyLocation?: string;
|
||||||
|
connectionLocation?: string;
|
||||||
homeId?: string;
|
homeId?: string;
|
||||||
activationCode?: string;
|
activationCode?: string;
|
||||||
phoneNumbers?: {
|
phoneNumbers?: {
|
||||||
|
|
@ -922,6 +954,9 @@ export declare function createContract(data: ContractCreateData): Promise<{
|
||||||
routerModel: string | null;
|
routerModel: string | null;
|
||||||
installationDate: Date | null;
|
installationDate: Date | null;
|
||||||
internetUsername: string | null;
|
internetUsername: string | null;
|
||||||
|
propertyType: string | null;
|
||||||
|
propertyLocation: string | null;
|
||||||
|
connectionLocation: string | null;
|
||||||
}) | null;
|
}) | null;
|
||||||
mobileDetails: ({
|
mobileDetails: ({
|
||||||
simCards: {
|
simCards: {
|
||||||
|
|
@ -1165,7 +1200,8 @@ export declare function updateContract(id: number, data: Partial<ContractCreateD
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
notes: string | null;
|
notes: string | null;
|
||||||
documentPath: string | null;
|
documentPath: string | null;
|
||||||
energyContractDetailsId: number;
|
energyContractDetailsId: number | null;
|
||||||
|
contractId: number | null;
|
||||||
invoiceDate: Date;
|
invoiceDate: Date;
|
||||||
invoiceType: import(".prisma/client").$Enums.InvoiceType;
|
invoiceType: import(".prisma/client").$Enums.InvoiceType;
|
||||||
}[];
|
}[];
|
||||||
|
|
@ -1294,7 +1330,8 @@ export declare function updateContract(id: number, data: Partial<ContractCreateD
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
notes: string | null;
|
notes: string | null;
|
||||||
documentPath: string | null;
|
documentPath: string | null;
|
||||||
energyContractDetailsId: number;
|
energyContractDetailsId: number | null;
|
||||||
|
contractId: number | null;
|
||||||
invoiceDate: Date;
|
invoiceDate: Date;
|
||||||
invoiceType: import(".prisma/client").$Enums.InvoiceType;
|
invoiceType: import(".prisma/client").$Enums.InvoiceType;
|
||||||
}[];
|
}[];
|
||||||
|
|
@ -1334,6 +1371,9 @@ export declare function updateContract(id: number, data: Partial<ContractCreateD
|
||||||
routerModel: string | null;
|
routerModel: string | null;
|
||||||
installationDate: Date | null;
|
installationDate: Date | null;
|
||||||
internetUsername: string | null;
|
internetUsername: string | null;
|
||||||
|
propertyType: string | null;
|
||||||
|
propertyLocation: string | null;
|
||||||
|
connectionLocation: string | null;
|
||||||
}) | null;
|
}) | null;
|
||||||
mobileDetails: ({
|
mobileDetails: ({
|
||||||
simCards: {
|
simCards: {
|
||||||
|
|
@ -1498,6 +1538,9 @@ export declare function updateContract(id: number, data: Partial<ContractCreateD
|
||||||
routerModel: string | null;
|
routerModel: string | null;
|
||||||
installationDate: Date | null;
|
installationDate: Date | null;
|
||||||
internetUsername: string | null;
|
internetUsername: string | null;
|
||||||
|
propertyType: string | null;
|
||||||
|
propertyLocation: string | null;
|
||||||
|
connectionLocation: string | null;
|
||||||
}) | null;
|
}) | null;
|
||||||
mobileDetails: ({
|
mobileDetails: ({
|
||||||
simCards: {
|
simCards: {
|
||||||
|
|
@ -1547,6 +1590,27 @@ export declare function updateContract(id: number, data: Partial<ContractCreateD
|
||||||
deductibleFull: number | null;
|
deductibleFull: number | null;
|
||||||
previousInsurer: string | null;
|
previousInsurer: string | null;
|
||||||
} | null;
|
} | null;
|
||||||
|
documents: {
|
||||||
|
id: number;
|
||||||
|
createdAt: Date;
|
||||||
|
notes: string | null;
|
||||||
|
documentPath: string;
|
||||||
|
contractId: number;
|
||||||
|
documentType: string;
|
||||||
|
originalName: string;
|
||||||
|
uploadedBy: string | null;
|
||||||
|
}[];
|
||||||
|
invoices: {
|
||||||
|
id: number;
|
||||||
|
createdAt: Date;
|
||||||
|
updatedAt: Date;
|
||||||
|
notes: string | null;
|
||||||
|
documentPath: string | null;
|
||||||
|
energyContractDetailsId: number | null;
|
||||||
|
contractId: number | null;
|
||||||
|
invoiceDate: Date;
|
||||||
|
invoiceType: import(".prisma/client").$Enums.InvoiceType;
|
||||||
|
}[];
|
||||||
} & {
|
} & {
|
||||||
portalPasswordEncrypted: string | null;
|
portalPasswordEncrypted: string | null;
|
||||||
id: number;
|
id: number;
|
||||||
|
|
@ -1737,6 +1801,9 @@ export declare function createFollowUpContract(previousContractId: number): Prom
|
||||||
routerModel: string | null;
|
routerModel: string | null;
|
||||||
installationDate: Date | null;
|
installationDate: Date | null;
|
||||||
internetUsername: string | null;
|
internetUsername: string | null;
|
||||||
|
propertyType: string | null;
|
||||||
|
propertyLocation: string | null;
|
||||||
|
connectionLocation: string | null;
|
||||||
}) | null;
|
}) | null;
|
||||||
mobileDetails: ({
|
mobileDetails: ({
|
||||||
simCards: {
|
simCards: {
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -138,6 +138,8 @@ async function getContractById(id, decryptPassword = false) {
|
||||||
tvDetails: true,
|
tvDetails: true,
|
||||||
carInsuranceDetails: true,
|
carInsuranceDetails: true,
|
||||||
stressfreiEmail: true,
|
stressfreiEmail: true,
|
||||||
|
invoices: { orderBy: { invoiceDate: 'desc' } },
|
||||||
|
documents: { orderBy: { createdAt: 'desc' } },
|
||||||
followUpContract: {
|
followUpContract: {
|
||||||
select: { id: true, contractNumber: true, status: true },
|
select: { id: true, contractNumber: true, status: true },
|
||||||
},
|
},
|
||||||
|
|
@ -183,6 +185,9 @@ async function createContract(data) {
|
||||||
internetPasswordEncrypted: internetDetails.internetPassword
|
internetPasswordEncrypted: internetDetails.internetPassword
|
||||||
? (0, encryption_js_1.encrypt)(internetDetails.internetPassword)
|
? (0, encryption_js_1.encrypt)(internetDetails.internetPassword)
|
||||||
: undefined,
|
: undefined,
|
||||||
|
propertyType: internetDetails.propertyType,
|
||||||
|
propertyLocation: internetDetails.propertyLocation,
|
||||||
|
connectionLocation: internetDetails.connectionLocation,
|
||||||
homeId: internetDetails.homeId,
|
homeId: internetDetails.homeId,
|
||||||
activationCode: internetDetails.activationCode,
|
activationCode: internetDetails.activationCode,
|
||||||
phoneNumbers: internetDetails.phoneNumbers && internetDetails.phoneNumbers.length > 0
|
phoneNumbers: internetDetails.phoneNumbers && internetDetails.phoneNumbers.length > 0
|
||||||
|
|
@ -321,6 +326,9 @@ async function updateContract(id, data) {
|
||||||
...(internetPassword
|
...(internetPassword
|
||||||
? { internetPasswordEncrypted: (0, encryption_js_1.encrypt)(internetPassword) }
|
? { internetPasswordEncrypted: (0, encryption_js_1.encrypt)(internetPassword) }
|
||||||
: {}),
|
: {}),
|
||||||
|
propertyType: internetData.propertyType,
|
||||||
|
propertyLocation: internetData.propertyLocation,
|
||||||
|
connectionLocation: internetData.connectionLocation,
|
||||||
homeId: internetData.homeId,
|
homeId: internetData.homeId,
|
||||||
activationCode: internetData.activationCode,
|
activationCode: internetData.activationCode,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -488,6 +488,17 @@ exports.Prisma.ContractScalarFieldEnum = {
|
||||||
updatedAt: 'updatedAt'
|
updatedAt: 'updatedAt'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.Prisma.ContractDocumentScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
contractId: 'contractId',
|
||||||
|
documentType: 'documentType',
|
||||||
|
documentPath: 'documentPath',
|
||||||
|
originalName: 'originalName',
|
||||||
|
notes: 'notes',
|
||||||
|
uploadedBy: 'uploadedBy',
|
||||||
|
createdAt: 'createdAt'
|
||||||
|
};
|
||||||
|
|
||||||
exports.Prisma.ContractHistoryEntryScalarFieldEnum = {
|
exports.Prisma.ContractHistoryEntryScalarFieldEnum = {
|
||||||
id: 'id',
|
id: 'id',
|
||||||
contractId: 'contractId',
|
contractId: 'contractId',
|
||||||
|
|
@ -551,6 +562,7 @@ exports.Prisma.ContractMeterScalarFieldEnum = {
|
||||||
exports.Prisma.InvoiceScalarFieldEnum = {
|
exports.Prisma.InvoiceScalarFieldEnum = {
|
||||||
id: 'id',
|
id: 'id',
|
||||||
energyContractDetailsId: 'energyContractDetailsId',
|
energyContractDetailsId: 'energyContractDetailsId',
|
||||||
|
contractId: 'contractId',
|
||||||
invoiceDate: 'invoiceDate',
|
invoiceDate: 'invoiceDate',
|
||||||
invoiceType: 'invoiceType',
|
invoiceType: 'invoiceType',
|
||||||
documentPath: 'documentPath',
|
documentPath: 'documentPath',
|
||||||
|
|
@ -569,6 +581,9 @@ exports.Prisma.InternetContractDetailsScalarFieldEnum = {
|
||||||
installationDate: 'installationDate',
|
installationDate: 'installationDate',
|
||||||
internetUsername: 'internetUsername',
|
internetUsername: 'internetUsername',
|
||||||
internetPasswordEncrypted: 'internetPasswordEncrypted',
|
internetPasswordEncrypted: 'internetPasswordEncrypted',
|
||||||
|
propertyType: 'propertyType',
|
||||||
|
propertyLocation: 'propertyLocation',
|
||||||
|
connectionLocation: 'connectionLocation',
|
||||||
homeId: 'homeId',
|
homeId: 'homeId',
|
||||||
activationCode: 'activationCode'
|
activationCode: 'activationCode'
|
||||||
};
|
};
|
||||||
|
|
@ -870,6 +885,7 @@ exports.Prisma.ModelName = {
|
||||||
Tariff: 'Tariff',
|
Tariff: 'Tariff',
|
||||||
ContractCategory: 'ContractCategory',
|
ContractCategory: 'ContractCategory',
|
||||||
Contract: 'Contract',
|
Contract: 'Contract',
|
||||||
|
ContractDocument: 'ContractDocument',
|
||||||
ContractHistoryEntry: 'ContractHistoryEntry',
|
ContractHistoryEntry: 'ContractHistoryEntry',
|
||||||
ContractTask: 'ContractTask',
|
ContractTask: 'ContractTask',
|
||||||
ContractTaskSubtask: 'ContractTaskSubtask',
|
ContractTaskSubtask: 'ContractTaskSubtask',
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "prisma-client-45a91d7556f300a75a0048d27fac6a72915779fc4e5c2234b54fe3547ddb1605",
|
"name": "prisma-client-f8de59fafbd0672a88c2a8e39308517de72556670ae690a7d472709948465d02",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"types": "index.d.ts",
|
"types": "index.d.ts",
|
||||||
"browser": "index-browser.js",
|
"browser": "index-browser.js",
|
||||||
|
|
|
||||||
|
|
@ -656,11 +656,29 @@ model Contract {
|
||||||
tasks ContractTask[]
|
tasks ContractTask[]
|
||||||
assignedEmails CachedEmail[] // Zugeordnete E-Mails aus dem E-Mail-Client
|
assignedEmails CachedEmail[] // Zugeordnete E-Mails aus dem E-Mail-Client
|
||||||
historyEntries ContractHistoryEntry[]
|
historyEntries ContractHistoryEntry[]
|
||||||
|
documents ContractDocument[]
|
||||||
|
invoices Invoice[]
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== CONTRACT DOCUMENTS ====================
|
||||||
|
|
||||||
|
model ContractDocument {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
contractId Int
|
||||||
|
contract Contract @relation(fields: [contractId], references: [id], onDelete: Cascade)
|
||||||
|
documentType String // Auftragsformular, Lieferbestätigung, etc.
|
||||||
|
documentPath String // Dateipfad
|
||||||
|
originalName String // Originaler Dateiname
|
||||||
|
notes String? @db.Text
|
||||||
|
uploadedBy String? // Wer hat hochgeladen
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
|
@@index([contractId])
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== CONTRACT HISTORY ====================
|
// ==================== CONTRACT HISTORY ====================
|
||||||
|
|
||||||
model ContractHistoryEntry {
|
model ContractHistoryEntry {
|
||||||
|
|
@ -752,17 +770,20 @@ model ContractMeter {
|
||||||
}
|
}
|
||||||
|
|
||||||
model Invoice {
|
model Invoice {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
energyContractDetailsId Int
|
energyContractDetailsId Int?
|
||||||
energyContractDetails EnergyContractDetails @relation(fields: [energyContractDetailsId], references: [id], onDelete: Cascade)
|
energyContractDetails EnergyContractDetails? @relation(fields: [energyContractDetailsId], references: [id], onDelete: Cascade)
|
||||||
|
contractId Int?
|
||||||
|
contract Contract? @relation(fields: [contractId], references: [id], onDelete: Cascade)
|
||||||
invoiceDate DateTime
|
invoiceDate DateTime
|
||||||
invoiceType InvoiceType
|
invoiceType InvoiceType
|
||||||
documentPath String? // Pflicht, außer bei NOT_AVAILABLE
|
documentPath String? // Pflicht, außer bei NOT_AVAILABLE
|
||||||
notes String?
|
notes String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
@@index([energyContractDetailsId])
|
@@index([energyContractDetailsId])
|
||||||
|
@@index([contractId])
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== INTERNET CONTRACT DETAILS ====================
|
// ==================== INTERNET CONTRACT DETAILS ====================
|
||||||
|
|
@ -779,6 +800,10 @@ model InternetContractDetails {
|
||||||
// Internet-Zugangsdaten
|
// Internet-Zugangsdaten
|
||||||
internetUsername String?
|
internetUsername String?
|
||||||
internetPasswordEncrypted String? // Verschlüsselt gespeichert
|
internetPasswordEncrypted String? // Verschlüsselt gespeichert
|
||||||
|
// Objekt & Lage
|
||||||
|
propertyType String? // Objekttyp (Mehrparteienhaus, Freistehendes Haus, etc.)
|
||||||
|
propertyLocation String? // Lage (Erdgeschoss, OG1, etc.)
|
||||||
|
connectionLocation String? // Lage des Anschlusses (Flur, HWR, etc.)
|
||||||
// Glasfaser-spezifisch
|
// Glasfaser-spezifisch
|
||||||
homeId String?
|
homeId String?
|
||||||
// Vodafone DSL/Kabel spezifisch
|
// Vodafone DSL/Kabel spezifisch
|
||||||
|
|
|
||||||
|
|
@ -488,6 +488,17 @@ exports.Prisma.ContractScalarFieldEnum = {
|
||||||
updatedAt: 'updatedAt'
|
updatedAt: 'updatedAt'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.Prisma.ContractDocumentScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
contractId: 'contractId',
|
||||||
|
documentType: 'documentType',
|
||||||
|
documentPath: 'documentPath',
|
||||||
|
originalName: 'originalName',
|
||||||
|
notes: 'notes',
|
||||||
|
uploadedBy: 'uploadedBy',
|
||||||
|
createdAt: 'createdAt'
|
||||||
|
};
|
||||||
|
|
||||||
exports.Prisma.ContractHistoryEntryScalarFieldEnum = {
|
exports.Prisma.ContractHistoryEntryScalarFieldEnum = {
|
||||||
id: 'id',
|
id: 'id',
|
||||||
contractId: 'contractId',
|
contractId: 'contractId',
|
||||||
|
|
@ -551,6 +562,7 @@ exports.Prisma.ContractMeterScalarFieldEnum = {
|
||||||
exports.Prisma.InvoiceScalarFieldEnum = {
|
exports.Prisma.InvoiceScalarFieldEnum = {
|
||||||
id: 'id',
|
id: 'id',
|
||||||
energyContractDetailsId: 'energyContractDetailsId',
|
energyContractDetailsId: 'energyContractDetailsId',
|
||||||
|
contractId: 'contractId',
|
||||||
invoiceDate: 'invoiceDate',
|
invoiceDate: 'invoiceDate',
|
||||||
invoiceType: 'invoiceType',
|
invoiceType: 'invoiceType',
|
||||||
documentPath: 'documentPath',
|
documentPath: 'documentPath',
|
||||||
|
|
@ -569,6 +581,9 @@ exports.Prisma.InternetContractDetailsScalarFieldEnum = {
|
||||||
installationDate: 'installationDate',
|
installationDate: 'installationDate',
|
||||||
internetUsername: 'internetUsername',
|
internetUsername: 'internetUsername',
|
||||||
internetPasswordEncrypted: 'internetPasswordEncrypted',
|
internetPasswordEncrypted: 'internetPasswordEncrypted',
|
||||||
|
propertyType: 'propertyType',
|
||||||
|
propertyLocation: 'propertyLocation',
|
||||||
|
connectionLocation: 'connectionLocation',
|
||||||
homeId: 'homeId',
|
homeId: 'homeId',
|
||||||
activationCode: 'activationCode'
|
activationCode: 'activationCode'
|
||||||
};
|
};
|
||||||
|
|
@ -870,6 +885,7 @@ exports.Prisma.ModelName = {
|
||||||
Tariff: 'Tariff',
|
Tariff: 'Tariff',
|
||||||
ContractCategory: 'ContractCategory',
|
ContractCategory: 'ContractCategory',
|
||||||
Contract: 'Contract',
|
Contract: 'Contract',
|
||||||
|
ContractDocument: 'ContractDocument',
|
||||||
ContractHistoryEntry: 'ContractHistoryEntry',
|
ContractHistoryEntry: 'ContractHistoryEntry',
|
||||||
ContractTask: 'ContractTask',
|
ContractTask: 'ContractTask',
|
||||||
ContractTaskSubtask: 'ContractTaskSubtask',
|
ContractTaskSubtask: 'ContractTaskSubtask',
|
||||||
|
|
|
||||||
|
|
@ -656,11 +656,29 @@ model Contract {
|
||||||
tasks ContractTask[]
|
tasks ContractTask[]
|
||||||
assignedEmails CachedEmail[] // Zugeordnete E-Mails aus dem E-Mail-Client
|
assignedEmails CachedEmail[] // Zugeordnete E-Mails aus dem E-Mail-Client
|
||||||
historyEntries ContractHistoryEntry[]
|
historyEntries ContractHistoryEntry[]
|
||||||
|
documents ContractDocument[]
|
||||||
|
invoices Invoice[]
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== CONTRACT DOCUMENTS ====================
|
||||||
|
|
||||||
|
model ContractDocument {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
contractId Int
|
||||||
|
contract Contract @relation(fields: [contractId], references: [id], onDelete: Cascade)
|
||||||
|
documentType String // Auftragsformular, Lieferbestätigung, etc.
|
||||||
|
documentPath String // Dateipfad
|
||||||
|
originalName String // Originaler Dateiname
|
||||||
|
notes String? @db.Text
|
||||||
|
uploadedBy String? // Wer hat hochgeladen
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
|
@@index([contractId])
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== CONTRACT HISTORY ====================
|
// ==================== CONTRACT HISTORY ====================
|
||||||
|
|
||||||
model ContractHistoryEntry {
|
model ContractHistoryEntry {
|
||||||
|
|
@ -753,8 +771,10 @@ model ContractMeter {
|
||||||
|
|
||||||
model Invoice {
|
model Invoice {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
energyContractDetailsId Int
|
energyContractDetailsId Int?
|
||||||
energyContractDetails EnergyContractDetails @relation(fields: [energyContractDetailsId], references: [id], onDelete: Cascade)
|
energyContractDetails EnergyContractDetails? @relation(fields: [energyContractDetailsId], references: [id], onDelete: Cascade)
|
||||||
|
contractId Int?
|
||||||
|
contract Contract? @relation(fields: [contractId], references: [id], onDelete: Cascade)
|
||||||
invoiceDate DateTime
|
invoiceDate DateTime
|
||||||
invoiceType InvoiceType
|
invoiceType InvoiceType
|
||||||
documentPath String? // Pflicht, außer bei NOT_AVAILABLE
|
documentPath String? // Pflicht, außer bei NOT_AVAILABLE
|
||||||
|
|
@ -763,6 +783,7 @@ model Invoice {
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
@@index([energyContractDetailsId])
|
@@index([energyContractDetailsId])
|
||||||
|
@@index([contractId])
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== INTERNET CONTRACT DETAILS ====================
|
// ==================== INTERNET CONTRACT DETAILS ====================
|
||||||
|
|
@ -779,6 +800,10 @@ model InternetContractDetails {
|
||||||
// Internet-Zugangsdaten
|
// Internet-Zugangsdaten
|
||||||
internetUsername String?
|
internetUsername String?
|
||||||
internetPasswordEncrypted String? // Verschlüsselt gespeichert
|
internetPasswordEncrypted String? // Verschlüsselt gespeichert
|
||||||
|
// Objekt & Lage
|
||||||
|
propertyType String? // Objekttyp (Mehrparteienhaus, Freistehendes Haus, etc.)
|
||||||
|
propertyLocation String? // Lage (Erdgeschoss, OG1, etc.)
|
||||||
|
connectionLocation String? // Lage des Anschlusses (Flur, HWR, etc.)
|
||||||
// Glasfaser-spezifisch
|
// Glasfaser-spezifisch
|
||||||
homeId String?
|
homeId String?
|
||||||
// Vodafone DSL/Kabel spezifisch
|
// Vodafone DSL/Kabel spezifisch
|
||||||
|
|
|
||||||
|
|
@ -410,6 +410,105 @@ export async function removeContractMeter(req: AuthRequest, res: Response): Prom
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== VERTRAGSDOKUMENTE ====================
|
||||||
|
|
||||||
|
export async function getContractDocuments(req: AuthRequest, res: Response): Promise<void> {
|
||||||
|
try {
|
||||||
|
const contractId = parseInt(req.params.id);
|
||||||
|
const documents = await prisma.contractDocument.findMany({
|
||||||
|
where: { contractId },
|
||||||
|
orderBy: { createdAt: 'desc' },
|
||||||
|
});
|
||||||
|
res.json({ success: true, data: documents } as ApiResponse);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ success: false, error: 'Fehler beim Laden der Dokumente' } as ApiResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function uploadContractDocument(req: AuthRequest, res: Response): Promise<void> {
|
||||||
|
try {
|
||||||
|
const contractId = parseInt(req.params.id);
|
||||||
|
const { documentType, notes } = req.body;
|
||||||
|
|
||||||
|
if (!req.file) {
|
||||||
|
res.status(400).json({ success: false, error: 'Keine Datei hochgeladen' } as ApiResponse);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!documentType) {
|
||||||
|
res.status(400).json({ success: false, error: 'Dokumenttyp erforderlich' } as ApiResponse);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const documentPath = `/uploads/contract-documents/${req.file.filename}`;
|
||||||
|
const doc = await prisma.contractDocument.create({
|
||||||
|
data: {
|
||||||
|
contractId,
|
||||||
|
documentType,
|
||||||
|
documentPath,
|
||||||
|
originalName: req.file.originalname,
|
||||||
|
notes: notes || null,
|
||||||
|
uploadedBy: req.user?.email,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const contract = await prisma.contract.findUnique({ where: { id: contractId }, select: { contractNumber: true, customerId: true } });
|
||||||
|
await logChange({
|
||||||
|
req, action: 'CREATE', resourceType: 'ContractDocument',
|
||||||
|
resourceId: doc.id.toString(),
|
||||||
|
label: `Dokument "${documentType}" hochgeladen für Vertrag ${contract?.contractNumber}`,
|
||||||
|
details: { typ: documentType, datei: req.file.originalname },
|
||||||
|
customerId: contract?.customerId,
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(201).json({ success: true, data: doc } as ApiResponse);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
error: error instanceof Error ? error.message : 'Fehler beim Hochladen',
|
||||||
|
} as ApiResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteContractDocument(req: AuthRequest, res: Response): Promise<void> {
|
||||||
|
try {
|
||||||
|
const documentId = parseInt(req.params.documentId);
|
||||||
|
const contractId = parseInt(req.params.id);
|
||||||
|
|
||||||
|
const doc = await prisma.contractDocument.findUnique({ where: { id: documentId } });
|
||||||
|
if (!doc || doc.contractId !== contractId) {
|
||||||
|
res.status(404).json({ success: false, error: 'Dokument nicht gefunden' } as ApiResponse);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Datei löschen
|
||||||
|
const fs = await import('fs');
|
||||||
|
const path = await import('path');
|
||||||
|
const filePath = path.join(process.cwd(), doc.documentPath);
|
||||||
|
if (fs.existsSync(filePath)) {
|
||||||
|
fs.unlinkSync(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.contractDocument.delete({ where: { id: documentId } });
|
||||||
|
|
||||||
|
const contract = await prisma.contract.findUnique({ where: { id: contractId }, select: { contractNumber: true, customerId: true } });
|
||||||
|
await logChange({
|
||||||
|
req, action: 'DELETE', resourceType: 'ContractDocument',
|
||||||
|
resourceId: documentId.toString(),
|
||||||
|
label: `Dokument "${doc.documentType}" gelöscht von Vertrag ${contract?.contractNumber}`,
|
||||||
|
details: { typ: doc.documentType, datei: doc.originalName },
|
||||||
|
customerId: contract?.customerId,
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json({ success: true, message: 'Dokument gelöscht' } as ApiResponse);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
error: error instanceof Error ? error.message : 'Fehler beim Löschen',
|
||||||
|
} as ApiResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== SNOOZE (VERTRAG ZURÜCKSTELLEN) ====================
|
// ==================== SNOOZE (VERTRAG ZURÜCKSTELLEN) ====================
|
||||||
|
|
||||||
export async function snoozeContract(req: Request, res: Response): Promise<void> {
|
export async function snoozeContract(req: Request, res: Response): Promise<void> {
|
||||||
|
|
|
||||||
|
|
@ -143,3 +143,38 @@ export async function deleteInvoice(req: Request, res: Response): Promise<void>
|
||||||
} as ApiResponse);
|
} as ApiResponse);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== CONTRACT-BASIERTE RECHNUNGEN (für alle Vertragstypen) ====================
|
||||||
|
|
||||||
|
export async function getInvoicesByContract(req: Request, res: Response): Promise<void> {
|
||||||
|
try {
|
||||||
|
const contractId = parseInt(req.params.id);
|
||||||
|
const invoices = await invoiceService.getInvoicesByContract(contractId);
|
||||||
|
res.json({ success: true, data: invoices } as ApiResponse);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ success: false, error: 'Fehler beim Laden der Rechnungen' } as ApiResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addInvoiceByContract(req: Request, res: Response): Promise<void> {
|
||||||
|
try {
|
||||||
|
const contractId = parseInt(req.params.id);
|
||||||
|
const { invoiceDate, invoiceType, notes } = req.body;
|
||||||
|
const invoice = await invoiceService.addInvoiceByContract(contractId, {
|
||||||
|
invoiceDate: new Date(invoiceDate),
|
||||||
|
invoiceType,
|
||||||
|
notes,
|
||||||
|
});
|
||||||
|
await logChange({
|
||||||
|
req, action: 'CREATE', resourceType: 'Invoice',
|
||||||
|
resourceId: invoice.id.toString(),
|
||||||
|
label: `Rechnung (${invoiceType}) hinzugefügt`,
|
||||||
|
});
|
||||||
|
res.status(201).json({ success: true, data: invoice } as ApiResponse);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
error: error instanceof Error ? error.message : 'Fehler beim Hinzufügen',
|
||||||
|
} as ApiResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,34 @@
|
||||||
import { Router } from 'express';
|
import { Router } from 'express';
|
||||||
|
import multer from 'multer';
|
||||||
|
import path from 'path';
|
||||||
|
import fs from 'fs';
|
||||||
import * as contractController from '../controllers/contract.controller.js';
|
import * as contractController from '../controllers/contract.controller.js';
|
||||||
|
import * as invoiceController from '../controllers/invoice.controller.js';
|
||||||
import { authenticate, requirePermission } from '../middleware/auth.js';
|
import { authenticate, requirePermission } from '../middleware/auth.js';
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
|
// Multer für Vertragsdokumente
|
||||||
|
const docUploadsDir = path.join(process.cwd(), 'uploads', 'contract-documents');
|
||||||
|
if (!fs.existsSync(docUploadsDir)) {
|
||||||
|
fs.mkdirSync(docUploadsDir, { recursive: true });
|
||||||
|
}
|
||||||
|
const docUpload = multer({
|
||||||
|
storage: multer.diskStorage({
|
||||||
|
destination: (_req, _file, cb) => cb(null, docUploadsDir),
|
||||||
|
filename: (_req, file, cb) => {
|
||||||
|
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
|
||||||
|
cb(null, `doc-${uniqueSuffix}${path.extname(file.originalname)}`);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
fileFilter: (_req, file, cb) => {
|
||||||
|
const allowed = ['application/pdf', 'image/jpeg', 'image/png', 'image/jpg'];
|
||||||
|
if (allowed.includes(file.mimetype)) cb(null, true);
|
||||||
|
else cb(new Error('Nur PDF, JPG und PNG Dateien sind erlaubt'));
|
||||||
|
},
|
||||||
|
limits: { fileSize: 10 * 1024 * 1024 },
|
||||||
|
});
|
||||||
|
|
||||||
router.get('/', authenticate, requirePermission('contracts:read'), contractController.getContracts);
|
router.get('/', authenticate, requirePermission('contracts:read'), contractController.getContracts);
|
||||||
router.post('/', authenticate, requirePermission('contracts:create'), contractController.createContract);
|
router.post('/', authenticate, requirePermission('contracts:create'), contractController.createContract);
|
||||||
|
|
||||||
|
|
@ -20,6 +45,15 @@ router.post('/:id/follow-up', authenticate, requirePermission('contracts:create'
|
||||||
// Snooze (Vertrag zurückstellen)
|
// Snooze (Vertrag zurückstellen)
|
||||||
router.patch('/:id/snooze', authenticate, requirePermission('contracts:update'), contractController.snoozeContract);
|
router.patch('/:id/snooze', authenticate, requirePermission('contracts:update'), contractController.snoozeContract);
|
||||||
|
|
||||||
|
// Rechnungen (für alle Vertragstypen)
|
||||||
|
router.get('/:id/invoices', authenticate, requirePermission('contracts:read'), invoiceController.getInvoicesByContract);
|
||||||
|
router.post('/:id/invoices', authenticate, requirePermission('contracts:update'), invoiceController.addInvoiceByContract);
|
||||||
|
|
||||||
|
// Vertragsdokumente
|
||||||
|
router.get('/:id/documents', authenticate, requirePermission('contracts:read'), contractController.getContractDocuments);
|
||||||
|
router.post('/:id/documents', authenticate, requirePermission('contracts:update'), docUpload.single('file'), contractController.uploadContractDocument);
|
||||||
|
router.delete('/:id/documents/:documentId', authenticate, requirePermission('contracts:update'), contractController.deleteContractDocument);
|
||||||
|
|
||||||
// Folgezähler
|
// Folgezähler
|
||||||
router.post('/:id/successor-meter', authenticate, requirePermission('contracts:update'), contractController.addSuccessorMeter);
|
router.post('/:id/successor-meter', authenticate, requirePermission('contracts:update'), contractController.addSuccessorMeter);
|
||||||
router.delete('/:id/contract-meter/:contractMeterId', authenticate, requirePermission('contracts:update'), contractController.removeContractMeter);
|
router.delete('/:id/contract-meter/:contractMeterId', authenticate, requirePermission('contracts:update'), contractController.removeContractMeter);
|
||||||
|
|
|
||||||
|
|
@ -137,6 +137,8 @@ export async function getContractById(id: number, decryptPassword = false) {
|
||||||
tvDetails: true,
|
tvDetails: true,
|
||||||
carInsuranceDetails: true,
|
carInsuranceDetails: true,
|
||||||
stressfreiEmail: true,
|
stressfreiEmail: true,
|
||||||
|
invoices: { orderBy: { invoiceDate: 'desc' as const } },
|
||||||
|
documents: { orderBy: { createdAt: 'desc' as const } },
|
||||||
followUpContract: {
|
followUpContract: {
|
||||||
select: { id: true, contractNumber: true, status: true },
|
select: { id: true, contractNumber: true, status: true },
|
||||||
},
|
},
|
||||||
|
|
@ -210,6 +212,10 @@ interface ContractCreateData {
|
||||||
// Internet-Zugangsdaten
|
// Internet-Zugangsdaten
|
||||||
internetUsername?: string;
|
internetUsername?: string;
|
||||||
internetPassword?: string;
|
internetPassword?: string;
|
||||||
|
// Objekt & Lage
|
||||||
|
propertyType?: string;
|
||||||
|
propertyLocation?: string;
|
||||||
|
connectionLocation?: string;
|
||||||
// Glasfaser-spezifisch
|
// Glasfaser-spezifisch
|
||||||
homeId?: string;
|
homeId?: string;
|
||||||
// Vodafone DSL/Kabel spezifisch
|
// Vodafone DSL/Kabel spezifisch
|
||||||
|
|
@ -302,6 +308,9 @@ export async function createContract(data: ContractCreateData) {
|
||||||
internetPasswordEncrypted: internetDetails.internetPassword
|
internetPasswordEncrypted: internetDetails.internetPassword
|
||||||
? encrypt(internetDetails.internetPassword)
|
? encrypt(internetDetails.internetPassword)
|
||||||
: undefined,
|
: undefined,
|
||||||
|
propertyType: internetDetails.propertyType,
|
||||||
|
propertyLocation: internetDetails.propertyLocation,
|
||||||
|
connectionLocation: internetDetails.connectionLocation,
|
||||||
homeId: internetDetails.homeId,
|
homeId: internetDetails.homeId,
|
||||||
activationCode: internetDetails.activationCode,
|
activationCode: internetDetails.activationCode,
|
||||||
phoneNumbers: internetDetails.phoneNumbers && internetDetails.phoneNumbers.length > 0
|
phoneNumbers: internetDetails.phoneNumbers && internetDetails.phoneNumbers.length > 0
|
||||||
|
|
@ -462,6 +471,9 @@ export async function updateContract(
|
||||||
...(internetPassword
|
...(internetPassword
|
||||||
? { internetPasswordEncrypted: encrypt(internetPassword) }
|
? { internetPasswordEncrypted: encrypt(internetPassword) }
|
||||||
: {}),
|
: {}),
|
||||||
|
propertyType: internetData.propertyType,
|
||||||
|
propertyLocation: internetData.propertyLocation,
|
||||||
|
connectionLocation: internetData.connectionLocation,
|
||||||
homeId: internetData.homeId,
|
homeId: internetData.homeId,
|
||||||
activationCode: internetData.activationCode,
|
activationCode: internetData.activationCode,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,7 @@ export async function addInvoice(energyContractDetailsId: number, data: CreateIn
|
||||||
return prisma.invoice.create({
|
return prisma.invoice.create({
|
||||||
data: {
|
data: {
|
||||||
energyContractDetailsId,
|
energyContractDetailsId,
|
||||||
|
contractId: energyDetails.contractId,
|
||||||
invoiceDate: data.invoiceDate,
|
invoiceDate: data.invoiceDate,
|
||||||
invoiceType: data.invoiceType,
|
invoiceType: data.invoiceType,
|
||||||
documentPath: data.documentPath,
|
documentPath: data.documentPath,
|
||||||
|
|
@ -67,6 +68,28 @@ export async function addInvoice(energyContractDetailsId: number, data: CreateIn
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rechnung direkt über contractId hinzufügen (für alle Vertragstypen)
|
||||||
|
*/
|
||||||
|
export async function addInvoiceByContract(contractId: number, data: CreateInvoiceData) {
|
||||||
|
return prisma.invoice.create({
|
||||||
|
data: {
|
||||||
|
contractId,
|
||||||
|
invoiceDate: data.invoiceDate,
|
||||||
|
invoiceType: data.invoiceType,
|
||||||
|
documentPath: data.documentPath,
|
||||||
|
notes: data.notes,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getInvoicesByContract(contractId: number) {
|
||||||
|
return prisma.invoice.findMany({
|
||||||
|
where: { contractId },
|
||||||
|
orderBy: { invoiceDate: 'desc' },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rechnung aktualisieren
|
* Rechnung aktualisieren
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ Vertragliste bei Energie mit Anschlussadresse/Lieferadresse noch in der Liste
|
||||||
Bei Mobilfunk die Mobilfunknummer und wenn vorhanden Karteninhaber
|
Bei Mobilfunk die Mobilfunknummer und wenn vorhanden Karteninhaber
|
||||||
Bei Festnetz, die Anschlussadresse/Lieferadresse
|
Bei Festnetz, die Anschlussadresse/Lieferadresse
|
||||||
Bei KFZ das Kennzeichen
|
Bei KFZ das Kennzeichen
|
||||||
|
#
|
||||||
|
|
||||||
#erledigt
|
#erledigt
|
||||||
Datenschutzerklärung wenn PDF hinterlegt wurde, alle Haken auf Grün setzten.
|
Datenschutzerklärung wenn PDF hinterlegt wurde, alle Haken auf Grün setzten.
|
||||||
|
|
@ -11,6 +12,7 @@ Aktuell zählt das PDF als Alternative zu den Online-Haken. Du willst es so:
|
||||||
PDF hochgeladen → alle 4 Online-Consents automatisch auf GRANTED setzen
|
PDF hochgeladen → alle 4 Online-Consents automatisch auf GRANTED setzen
|
||||||
Kunde entfernt einen Haken im Portal → PDF löschen + Tabs sperren
|
Kunde entfernt einen Haken im Portal → PDF löschen + Tabs sperren
|
||||||
Entsperrung nur durch: alle Haken wieder setzen ODER neues PDF hochladen
|
Entsperrung nur durch: alle Haken wieder setzen ODER neues PDF hochladen
|
||||||
|
#
|
||||||
|
|
||||||
#erledigt
|
#erledigt
|
||||||
Zweitarif (Gibt es auch 3 Tarifuzähler?) Zähler HT/NT bei Strom Zähler hinzufügen.
|
Zweitarif (Gibt es auch 3 Tarifuzähler?) Zähler HT/NT bei Strom Zähler hinzufügen.
|
||||||
|
|
@ -21,24 +23,36 @@ Alle Datumsfelder mit 0 davor wenn es ne einstellige Zahl ist
|
||||||
|
|
||||||
Jetzt : 1.1.2026
|
Jetzt : 1.1.2026
|
||||||
Und gewollt 01.01.2026
|
Und gewollt 01.01.2026
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
#erledigt
|
#erledigt
|
||||||
Die Auditmeldungen aussagekräftig
|
Die Auditmeldungen aussagekräftig
|
||||||
|
#
|
||||||
|
|
||||||
Email Log und system testen
|
Email Log und system testen
|
||||||
Sprich senden und Empfnagen
|
Sprich senden und Empfnagen
|
||||||
|
#
|
||||||
|
|
||||||
Security System testen
|
Security System testen
|
||||||
|
#
|
||||||
|
|
||||||
|
#erledigt
|
||||||
Datenschutzerklärung Website unserer Seite und ein impressum im Kundenportal.
|
Datenschutzerklärung Website unserer Seite und ein impressum im Kundenportal.
|
||||||
|
|
||||||
Auch wieder über das Einstellungsmenü editirerbar.
|
Auch wieder über das Einstellungsmenü editirerbar.
|
||||||
Bitte mach mir da auch einen Vorschagstext rein
|
Bitte mach mir da auch einen Vorschagstext rein
|
||||||
|
#
|
||||||
|
|
||||||
Geburtstagskalender, und Geburtgsgruß als Modal beim ersten Login an dem Tag,
|
Geburtstagskalender, und Geburtgsgruß als Modal beim ersten Login an dem Tag,
|
||||||
Sollte der Login bis n7 btage nach Geburtsag sein dann Glückwunsch nachträglich
|
Sollte der Login bis n7 btage nach Geburtsag sein dann Glückwunsch nachträglich
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
Email datenschutzerklärung erst wenn alle hebel drin sind, auf einen bestätigungsbutton klicken, um sicherzustellen, das alle heben drin sind.
|
||||||
|
Danch bestätigen, nochmals eine Bestätigiguns emails enden
|
||||||
|
#
|
||||||
|
|
||||||
|
Haben wir bei den Vertragen (also alle) kein Dokumentfeld zum Upload von, Auftragsformular, Lieferbestätigung, Vertragsunterlagen.
|
||||||
|
hier sind wieder png,pdf erlaubt
|
||||||
|
#
|
||||||
|
|
@ -17,15 +17,17 @@ const invoiceTypeLabels: Record<InvoiceType, string> = {
|
||||||
};
|
};
|
||||||
|
|
||||||
interface InvoicesSectionProps {
|
interface InvoicesSectionProps {
|
||||||
ecdId: number; // energyContractDetailsId
|
ecdId?: number; // energyContractDetailsId (optional - für Energie-Verträge)
|
||||||
invoices: Invoice[];
|
invoices: Invoice[];
|
||||||
contractId: number;
|
contractId: number;
|
||||||
canEdit: boolean;
|
canEdit: boolean;
|
||||||
|
showInvoiceWarnings?: boolean; // Warnungen für fehlende Schluss-/Zwischenrechnung (nur Energie)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function InvoicesSection({
|
export default function InvoicesSection({
|
||||||
ecdId,
|
ecdId,
|
||||||
invoices,
|
invoices,
|
||||||
|
showInvoiceWarnings = false,
|
||||||
contractId,
|
contractId,
|
||||||
canEdit,
|
canEdit,
|
||||||
}: InvoicesSectionProps) {
|
}: InvoicesSectionProps) {
|
||||||
|
|
@ -35,9 +37,9 @@ export default function InvoicesSection({
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const deleteInvoiceMutation = useMutation({
|
const deleteInvoiceMutation = useMutation({
|
||||||
mutationFn: (invoiceId: number) => invoiceApi.deleteInvoice(ecdId, invoiceId),
|
mutationFn: (invoiceId: number) => ecdId ? invoiceApi.deleteInvoice(ecdId, invoiceId) : invoiceApi.deleteInvoice(0, invoiceId),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['contract', contractId.toString()] });
|
queryClient.invalidateQueries({ queryKey: ['contract'] });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -56,18 +58,18 @@ export default function InvoicesSection({
|
||||||
<FileText className="w-4 h-4 text-gray-500" />
|
<FileText className="w-4 h-4 text-gray-500" />
|
||||||
<h4 className="text-sm font-medium text-gray-700">Rechnungen</h4>
|
<h4 className="text-sm font-medium text-gray-700">Rechnungen</h4>
|
||||||
<Badge variant="default">{invoices.length}</Badge>
|
<Badge variant="default">{invoices.length}</Badge>
|
||||||
{/* Status-Indicator */}
|
{/* Status-Indicator (nur bei Energie-Verträgen) */}
|
||||||
{hasFinalInvoice ? (
|
{showInvoiceWarnings && hasFinalInvoice ? (
|
||||||
<span className="flex items-center gap-1 px-2 py-0.5 text-xs rounded-full bg-green-100 text-green-800">
|
<span className="flex items-center gap-1 px-2 py-0.5 text-xs rounded-full bg-green-100 text-green-800">
|
||||||
<Check className="w-3 h-3" />
|
<Check className="w-3 h-3" />
|
||||||
Schlussrechnung
|
Schlussrechnung
|
||||||
</span>
|
</span>
|
||||||
) : hasNotAvailable ? (
|
) : showInvoiceWarnings && hasNotAvailable ? (
|
||||||
<span className="flex items-center gap-1 px-2 py-0.5 text-xs rounded-full bg-yellow-100 text-yellow-800">
|
<span className="flex items-center gap-1 px-2 py-0.5 text-xs rounded-full bg-yellow-100 text-yellow-800">
|
||||||
<AlertTriangle className="w-3 h-3" />
|
<AlertTriangle className="w-3 h-3" />
|
||||||
Nicht verfügbar
|
Nicht verfügbar
|
||||||
</span>
|
</span>
|
||||||
) : invoices.length > 0 ? (
|
) : showInvoiceWarnings && invoices.length > 0 ? (
|
||||||
<span className="flex items-center gap-1 px-2 py-0.5 text-xs rounded-full bg-orange-100 text-orange-800">
|
<span className="flex items-center gap-1 px-2 py-0.5 text-xs rounded-full bg-orange-100 text-orange-800">
|
||||||
<AlertTriangle className="w-3 h-3" />
|
<AlertTriangle className="w-3 h-3" />
|
||||||
Schlussrechnung fehlt
|
Schlussrechnung fehlt
|
||||||
|
|
@ -198,7 +200,7 @@ function InvoiceModal({
|
||||||
}: {
|
}: {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
ecdId: number;
|
ecdId?: number;
|
||||||
contractId: number;
|
contractId: number;
|
||||||
invoice?: Invoice | null;
|
invoice?: Invoice | null;
|
||||||
}) {
|
}) {
|
||||||
|
|
@ -216,10 +218,16 @@ function InvoiceModal({
|
||||||
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const addInvoiceFn = async (data: { invoiceDate: string; invoiceType: string; notes?: string }) => {
|
||||||
|
if (ecdId) {
|
||||||
|
return invoiceApi.addInvoice(ecdId, data as any);
|
||||||
|
}
|
||||||
|
return invoiceApi.addInvoiceByContract(contractId, data as any);
|
||||||
|
};
|
||||||
|
|
||||||
const createMutation = useMutation({
|
const createMutation = useMutation({
|
||||||
mutationFn: async (file: File) => {
|
mutationFn: async (file: File) => {
|
||||||
// 1. Invoice erstellen
|
const result = await addInvoiceFn({
|
||||||
const result = await invoiceApi.addInvoice(ecdId, {
|
|
||||||
invoiceDate: formData.invoiceDate,
|
invoiceDate: formData.invoiceDate,
|
||||||
invoiceType: formData.invoiceType,
|
invoiceType: formData.invoiceType,
|
||||||
notes: formData.notes || undefined,
|
notes: formData.notes || undefined,
|
||||||
|
|
@ -233,7 +241,7 @@ function InvoiceModal({
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['contract', contractId.toString()] });
|
queryClient.invalidateQueries({ queryKey: ['contract'] });
|
||||||
onClose();
|
onClose();
|
||||||
},
|
},
|
||||||
onError: (err: Error) => {
|
onError: (err: Error) => {
|
||||||
|
|
@ -243,15 +251,14 @@ function InvoiceModal({
|
||||||
|
|
||||||
const createWithoutFileMutation = useMutation({
|
const createWithoutFileMutation = useMutation({
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
// Für NOT_AVAILABLE Typ - kein Dokument erforderlich
|
return addInvoiceFn({
|
||||||
return await invoiceApi.addInvoice(ecdId, {
|
|
||||||
invoiceDate: formData.invoiceDate,
|
invoiceDate: formData.invoiceDate,
|
||||||
invoiceType: formData.invoiceType,
|
invoiceType: formData.invoiceType,
|
||||||
notes: formData.notes || undefined,
|
notes: formData.notes || undefined,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['contract', contractId.toString()] });
|
queryClient.invalidateQueries({ queryKey: ['contract'] });
|
||||||
onClose();
|
onClose();
|
||||||
},
|
},
|
||||||
onError: (err: Error) => {
|
onError: (err: Error) => {
|
||||||
|
|
@ -262,7 +269,7 @@ function InvoiceModal({
|
||||||
const updateMutation = useMutation({
|
const updateMutation = useMutation({
|
||||||
mutationFn: async (file: File | null) => {
|
mutationFn: async (file: File | null) => {
|
||||||
// 1. Invoice aktualisieren
|
// 1. Invoice aktualisieren
|
||||||
const result = await invoiceApi.updateInvoice(ecdId, invoice!.id, {
|
const result = await invoiceApi.updateInvoice(ecdId || 0, invoice!.id, {
|
||||||
invoiceDate: formData.invoiceDate,
|
invoiceDate: formData.invoiceDate,
|
||||||
invoiceType: formData.invoiceType,
|
invoiceType: formData.invoiceType,
|
||||||
notes: formData.notes || undefined,
|
notes: formData.notes || undefined,
|
||||||
|
|
@ -276,7 +283,7 @@ function InvoiceModal({
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['contract', contractId.toString()] });
|
queryClient.invalidateQueries({ queryKey: ['contract'] });
|
||||||
onClose();
|
onClose();
|
||||||
},
|
},
|
||||||
onError: (err: Error) => {
|
onError: (err: Error) => {
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,11 @@ import Badge from '../../components/ui/Badge';
|
||||||
import Input from '../../components/ui/Input';
|
import Input from '../../components/ui/Input';
|
||||||
import Modal from '../../components/ui/Modal';
|
import Modal from '../../components/ui/Modal';
|
||||||
import FileUpload from '../../components/ui/FileUpload';
|
import FileUpload from '../../components/ui/FileUpload';
|
||||||
import { Edit, Trash2, Copy, Eye, EyeOff, ArrowLeft, ArrowRight, Download, ExternalLink, Plus, ChevronDown, ChevronUp, Gauge, CheckCircle, Circle, ClipboardList, MessageSquare, Calculator, Info, X, BellOff, Lock, Shield } from 'lucide-react';
|
import { Edit, Trash2, Copy, Eye, EyeOff, ArrowLeft, ArrowRight, Download, ExternalLink, Plus, ChevronDown, ChevronUp, Gauge, CheckCircle, Circle, ClipboardList, MessageSquare, Calculator, Info, X, BellOff, Lock, Shield, FileText } from 'lucide-react';
|
||||||
import { calculateConsumption, calculateCosts, calculateMultiMeterConsumption } from '../../utils/energyCalculations';
|
import { calculateConsumption, calculateCosts, calculateMultiMeterConsumption } from '../../utils/energyCalculations';
|
||||||
import CopyButton, { CopyableBlock } from '../../components/ui/CopyButton';
|
import CopyButton, { CopyableBlock } from '../../components/ui/CopyButton';
|
||||||
import { formatDate } from '../../utils/dateFormat';
|
import { formatDate } from '../../utils/dateFormat';
|
||||||
import type { ContractType, ContractStatus, SimCard, MeterReading, ContractTask, ContractTaskSubtask, ContractMeter } from '../../types';
|
import type { ContractType, ContractStatus, SimCard, MeterReading, ContractTask, ContractTaskSubtask, ContractMeter, ContractDocument } from '../../types';
|
||||||
|
|
||||||
const typeLabels: Record<ContractType, string> = {
|
const typeLabels: Record<ContractType, string> = {
|
||||||
ELECTRICITY: 'Strom',
|
ELECTRICITY: 'Strom',
|
||||||
|
|
@ -2576,6 +2576,7 @@ export default function ContractDetail() {
|
||||||
invoices={c.energyDetails.invoices || []}
|
invoices={c.energyDetails.invoices || []}
|
||||||
contractId={contractId}
|
contractId={contractId}
|
||||||
canEdit={hasPermission('contracts:update') && !isCustomer}
|
canEdit={hasPermission('contracts:update') && !isCustomer}
|
||||||
|
showInvoiceWarnings={true}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
@ -2614,6 +2615,24 @@ export default function ContractDetail() {
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{c.internetDetails.propertyType && (
|
||||||
|
<div>
|
||||||
|
<dt className="text-sm text-gray-500">Objekttyp</dt>
|
||||||
|
<dd>{c.internetDetails.propertyType}</dd>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{c.internetDetails.propertyLocation && (
|
||||||
|
<div>
|
||||||
|
<dt className="text-sm text-gray-500">Lage</dt>
|
||||||
|
<dd>{c.internetDetails.propertyLocation}</dd>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{c.internetDetails.connectionLocation && (
|
||||||
|
<div>
|
||||||
|
<dt className="text-sm text-gray-500">Anschluss-Lage</dt>
|
||||||
|
<dd>{c.internetDetails.connectionLocation}</dd>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{c.internetDetails.installationDate && (
|
{c.internetDetails.installationDate && (
|
||||||
<div>
|
<div>
|
||||||
<dt className="text-sm text-gray-500">Installation</dt>
|
<dt className="text-sm text-gray-500">Installation</dt>
|
||||||
|
|
@ -2960,6 +2979,25 @@ export default function ContractDetail() {
|
||||||
isCustomerPortal={isCustomerPortal}
|
isCustomerPortal={isCustomerPortal}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Rechnungen (bei allen Vertragstypen, außer Energie - die haben ihre eigene Section) */}
|
||||||
|
{!['ELECTRICITY', 'GAS'].includes(c.type) && !isCustomerPortal && (
|
||||||
|
<Card className="mb-6">
|
||||||
|
<InvoicesSection
|
||||||
|
invoices={c.invoices || []}
|
||||||
|
contractId={contractId}
|
||||||
|
canEdit={hasPermission('contracts:update')}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Vertragsdokumente */}
|
||||||
|
{!isCustomerPortal && (
|
||||||
|
<ContractDocumentsSection
|
||||||
|
contractId={contractId}
|
||||||
|
canEdit={hasPermission('contracts:update')}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Zugeordnete E-Mails */}
|
{/* Zugeordnete E-Mails */}
|
||||||
{!isCustomerPortal && hasPermission('contracts:read') && c.customerId && (
|
{!isCustomerPortal && hasPermission('contracts:read') && c.customerId && (
|
||||||
<ContractEmailsSection
|
<ContractEmailsSection
|
||||||
|
|
@ -3057,3 +3095,177 @@ export default function ContractDetail() {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== VERTRAGSDOKUMENTE ====================
|
||||||
|
|
||||||
|
const DOCUMENT_TYPES = [
|
||||||
|
'Auftragsformular',
|
||||||
|
'Auftragsbestätigung',
|
||||||
|
'Lieferbestätigung',
|
||||||
|
'Vertragsunterlagen',
|
||||||
|
'Vollmacht',
|
||||||
|
'Widerrufsbelehrung',
|
||||||
|
'Preisblatt',
|
||||||
|
'Sonstiges',
|
||||||
|
];
|
||||||
|
|
||||||
|
function ContractDocumentsSection({
|
||||||
|
contractId,
|
||||||
|
canEdit,
|
||||||
|
}: {
|
||||||
|
contractId: number;
|
||||||
|
canEdit: boolean;
|
||||||
|
}) {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const [showUpload, setShowUpload] = useState(false);
|
||||||
|
const [uploadType, setUploadType] = useState(DOCUMENT_TYPES[0]);
|
||||||
|
const [uploadNotes, setUploadNotes] = useState('');
|
||||||
|
|
||||||
|
const { data: docsData } = useQuery({
|
||||||
|
queryKey: ['contract-documents', contractId],
|
||||||
|
queryFn: () => contractApi.getDocuments(contractId),
|
||||||
|
});
|
||||||
|
|
||||||
|
const uploadMutation = useMutation({
|
||||||
|
mutationFn: ({ file, documentType, notes }: { file: File; documentType: string; notes?: string }) =>
|
||||||
|
contractApi.uploadDocument(contractId, file, documentType, notes),
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['contract-documents', contractId] });
|
||||||
|
setShowUpload(false);
|
||||||
|
setUploadNotes('');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const deleteMutation = useMutation({
|
||||||
|
mutationFn: (documentId: number) => contractApi.deleteDocument(contractId, documentId),
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['contract-documents', contractId] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const documents: ContractDocument[] = docsData?.data || [];
|
||||||
|
|
||||||
|
const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const file = e.target.files?.[0];
|
||||||
|
if (file) {
|
||||||
|
uploadMutation.mutate({ file, documentType: uploadType, notes: uploadNotes || undefined });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="mb-6" title={
|
||||||
|
<div className="flex items-center justify-between w-full">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<FileText className="w-5 h-5" />
|
||||||
|
<span>Vertragsdokumente</span>
|
||||||
|
<span className="text-sm font-normal text-gray-500">({documents.length})</span>
|
||||||
|
</div>
|
||||||
|
{canEdit && (
|
||||||
|
<Button variant="ghost" size="sm" onClick={() => setShowUpload(!showUpload)}>
|
||||||
|
<Plus className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
}>
|
||||||
|
{/* Upload-Bereich */}
|
||||||
|
{showUpload && (
|
||||||
|
<div className="mb-4 p-4 bg-blue-50 border border-blue-200 rounded-lg">
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3 mb-3">
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-1">Dokumenttyp</label>
|
||||||
|
<select
|
||||||
|
value={uploadType}
|
||||||
|
onChange={(e) => setUploadType(e.target.value)}
|
||||||
|
className="block w-full px-3 py-2 border border-gray-300 rounded-lg text-sm"
|
||||||
|
>
|
||||||
|
{DOCUMENT_TYPES.map((t) => (
|
||||||
|
<option key={t} value={t}>{t}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div className="sm:col-span-2">
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-1">Notiz (optional)</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={uploadNotes}
|
||||||
|
onChange={(e) => setUploadNotes(e.target.value)}
|
||||||
|
placeholder="z.B. Unterschrieben am 15.03.2026"
|
||||||
|
className="block w-full px-3 py-2 border border-gray-300 rounded-lg text-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<label className="inline-flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 cursor-pointer text-sm">
|
||||||
|
<Plus className="w-4 h-4" />
|
||||||
|
Datei wählen (PDF, JPG, PNG)
|
||||||
|
<input type="file" accept=".pdf,.jpg,.jpeg,.png" className="hidden" onChange={handleFileSelect} />
|
||||||
|
</label>
|
||||||
|
<Button variant="secondary" size="sm" onClick={() => setShowUpload(false)}>Abbrechen</Button>
|
||||||
|
{uploadMutation.isPending && <span className="text-sm text-gray-500">Hochladen...</span>}
|
||||||
|
</div>
|
||||||
|
{uploadMutation.isError && (
|
||||||
|
<p className="text-xs text-red-600 mt-2">Fehler beim Hochladen</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Dokumentliste */}
|
||||||
|
{documents.length === 0 ? (
|
||||||
|
<p className="text-sm text-gray-500">Keine Dokumente vorhanden.</p>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-2">
|
||||||
|
{documents.map((doc) => (
|
||||||
|
<div key={doc.id} className="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<FileText className="w-4 h-4 text-gray-400" />
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-xs px-2 py-0.5 rounded-full bg-blue-100 text-blue-700">
|
||||||
|
{doc.documentType}
|
||||||
|
</span>
|
||||||
|
<a
|
||||||
|
href={`/api${doc.documentPath}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-sm text-blue-600 hover:underline"
|
||||||
|
>
|
||||||
|
{doc.originalName}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3 mt-0.5 text-xs text-gray-500">
|
||||||
|
<span>{formatDate(doc.createdAt)}</span>
|
||||||
|
{doc.uploadedBy && <span>von {doc.uploadedBy}</span>}
|
||||||
|
{doc.notes && <span>– {doc.notes}</span>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<a
|
||||||
|
href={`/api${doc.documentPath}`}
|
||||||
|
download
|
||||||
|
className="text-gray-400 hover:text-blue-600"
|
||||||
|
title="Herunterladen"
|
||||||
|
>
|
||||||
|
<Download className="w-4 h-4" />
|
||||||
|
</a>
|
||||||
|
{canEdit && (
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
if (confirm(`Dokument "${doc.originalName}" wirklich löschen?`)) {
|
||||||
|
deleteMutation.mutate(doc.id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="text-gray-400 hover:text-red-600"
|
||||||
|
title="Löschen"
|
||||||
|
>
|
||||||
|
<Trash2 className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -288,6 +288,9 @@ export default function ContractForm() {
|
||||||
routerSerialNumber: c.internetDetails?.routerSerialNumber || '',
|
routerSerialNumber: c.internetDetails?.routerSerialNumber || '',
|
||||||
installationDate: c.internetDetails?.installationDate ? c.internetDetails.installationDate.split('T')[0] : '',
|
installationDate: c.internetDetails?.installationDate ? c.internetDetails.installationDate.split('T')[0] : '',
|
||||||
internetUsername: c.internetDetails?.internetUsername || '',
|
internetUsername: c.internetDetails?.internetUsername || '',
|
||||||
|
propertyType: c.internetDetails?.propertyType || '',
|
||||||
|
propertyLocation: c.internetDetails?.propertyLocation || '',
|
||||||
|
connectionLocation: c.internetDetails?.connectionLocation || '',
|
||||||
homeId: c.internetDetails?.homeId || '',
|
homeId: c.internetDetails?.homeId || '',
|
||||||
activationCode: c.internetDetails?.activationCode || '',
|
activationCode: c.internetDetails?.activationCode || '',
|
||||||
// Mobile details
|
// Mobile details
|
||||||
|
|
@ -531,6 +534,10 @@ export default function ContractForm() {
|
||||||
// Internet-Zugangsdaten
|
// Internet-Zugangsdaten
|
||||||
internetUsername: emptyToNull(data.internetUsername),
|
internetUsername: emptyToNull(data.internetUsername),
|
||||||
internetPassword: data.internetPassword || undefined, // Passwort: undefined = nicht ändern
|
internetPassword: data.internetPassword || undefined, // Passwort: undefined = nicht ändern
|
||||||
|
// Objekt & Lage
|
||||||
|
propertyType: emptyToNull(data.propertyType),
|
||||||
|
propertyLocation: emptyToNull(data.propertyLocation),
|
||||||
|
connectionLocation: emptyToNull(data.connectionLocation),
|
||||||
// Glasfaser-spezifisch
|
// Glasfaser-spezifisch
|
||||||
homeId: emptyToNull(data.homeId),
|
homeId: emptyToNull(data.homeId),
|
||||||
// Vodafone DSL/Kabel spezifisch
|
// Vodafone DSL/Kabel spezifisch
|
||||||
|
|
@ -1027,6 +1034,65 @@ export default function ContractForm() {
|
||||||
value={watch('installationDate') || ''}
|
value={watch('installationDate') || ''}
|
||||||
onClear={() => setValue('installationDate', '')}
|
onClear={() => setValue('installationDate', '')}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Objekt & Lage */}
|
||||||
|
<h4 className="text-sm font-medium text-gray-700 mt-4 mb-2">Objekt & Lage</h4>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
|
<Select
|
||||||
|
label="Objekttyp"
|
||||||
|
{...register('propertyType')}
|
||||||
|
options={[
|
||||||
|
{ value: '', label: 'Bitte wählen...' },
|
||||||
|
{ value: 'Mehrparteienhaus', label: 'Mehrparteienhaus' },
|
||||||
|
{ value: 'Freistehendes Haus', label: 'Freistehendes Haus' },
|
||||||
|
{ value: 'Doppelhaushälfte', label: 'Doppelhaushälfte' },
|
||||||
|
{ value: 'Reihenhaus', label: 'Reihenhaus' },
|
||||||
|
{ value: 'Wohnung', label: 'Wohnung' },
|
||||||
|
{ value: 'Bürogebäude', label: 'Bürogebäude' },
|
||||||
|
{ value: 'Gewerbeeinheit', label: 'Gewerbeeinheit' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
label="Lage"
|
||||||
|
{...register('propertyLocation')}
|
||||||
|
options={[
|
||||||
|
{ value: '', label: 'Bitte wählen...' },
|
||||||
|
{ value: 'Vorderhaus', label: 'Vorderhaus' },
|
||||||
|
{ value: 'Hinterhaus', label: 'Hinterhaus' },
|
||||||
|
{ value: 'Links', label: 'Links' },
|
||||||
|
{ value: 'Rechts', label: 'Rechts' },
|
||||||
|
{ value: 'Mitte', label: 'Mitte' },
|
||||||
|
{ value: 'Keller', label: 'Keller' },
|
||||||
|
{ value: 'Souterrain', label: 'Souterrain' },
|
||||||
|
{ value: 'Erdgeschoss', label: 'Erdgeschoss' },
|
||||||
|
...[...Array(25)].map((_, i) => ({ value: `${i + 1}. OG`, label: `${i + 1}. Obergeschoss` })),
|
||||||
|
{ value: 'Dachgeschoss', label: 'Dachgeschoss' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
label="Lage des Anschlusses"
|
||||||
|
{...register('connectionLocation')}
|
||||||
|
options={[
|
||||||
|
{ value: '', label: 'Bitte wählen...' },
|
||||||
|
{ value: 'Flur', label: 'Flur' },
|
||||||
|
{ value: 'Wohnzimmer', label: 'Wohnzimmer' },
|
||||||
|
{ value: 'Schlafzimmer', label: 'Schlafzimmer' },
|
||||||
|
{ value: 'Kinderzimmer', label: 'Kinderzimmer' },
|
||||||
|
{ value: 'Küche', label: 'Küche' },
|
||||||
|
{ value: 'Büro', label: 'Büro' },
|
||||||
|
{ value: 'HWR', label: 'Hauswirtschaftsraum (HWR)' },
|
||||||
|
{ value: 'Hausanschlussraum', label: 'Hausanschlussraum' },
|
||||||
|
{ value: 'Abstellraum', label: 'Abstellraum' },
|
||||||
|
{ value: 'Garage', label: 'Garage' },
|
||||||
|
{ value: 'Serverraum', label: 'Serverraum' },
|
||||||
|
{ value: 'Empfang', label: 'Empfang / Rezeption' },
|
||||||
|
{ value: 'Keller', label: 'Keller' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
|
||||||
{/* HomeID nur bei Glasfaser */}
|
{/* HomeID nur bei Glasfaser */}
|
||||||
{contractType === 'FIBER' && (
|
{contractType === 'FIBER' && (
|
||||||
<Input label="Home-ID" {...register('homeId')} />
|
<Input label="Home-ID" {...register('homeId')} />
|
||||||
|
|
|
||||||
|
|
@ -232,6 +232,10 @@ export const invoiceApi = {
|
||||||
const res = await api.post<ApiResponse<Invoice>>(`/energy-details/${ecdId}/invoices`, data);
|
const res = await api.post<ApiResponse<Invoice>>(`/energy-details/${ecdId}/invoices`, data);
|
||||||
return res.data;
|
return res.data;
|
||||||
},
|
},
|
||||||
|
addInvoiceByContract: async (contractId: number, data: Partial<Invoice>) => {
|
||||||
|
const res = await api.post<ApiResponse<Invoice>>(`/contracts/${contractId}/invoices`, data);
|
||||||
|
return res.data;
|
||||||
|
},
|
||||||
updateInvoice: async (ecdId: number, invoiceId: number, data: Partial<Invoice>) => {
|
updateInvoice: async (ecdId: number, invoiceId: number, data: Partial<Invoice>) => {
|
||||||
const res = await api.put<ApiResponse<Invoice>>(`/energy-details/${ecdId}/invoices/${invoiceId}`, data);
|
const res = await api.put<ApiResponse<Invoice>>(`/energy-details/${ecdId}/invoices/${invoiceId}`, data);
|
||||||
return res.data;
|
return res.data;
|
||||||
|
|
@ -655,6 +659,25 @@ export const contractApi = {
|
||||||
const res = await api.get<ApiResponse<import('../types').CockpitResult>>('/contracts/cockpit');
|
const res = await api.get<ApiResponse<import('../types').CockpitResult>>('/contracts/cockpit');
|
||||||
return res.data;
|
return res.data;
|
||||||
},
|
},
|
||||||
|
// Vertragsdokumente
|
||||||
|
getDocuments: async (contractId: number) => {
|
||||||
|
const res = await api.get<ApiResponse<import('../types').ContractDocument[]>>(`/contracts/${contractId}/documents`);
|
||||||
|
return res.data;
|
||||||
|
},
|
||||||
|
uploadDocument: async (contractId: number, file: File, documentType: string, notes?: string) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
formData.append('documentType', documentType);
|
||||||
|
if (notes) formData.append('notes', notes);
|
||||||
|
const res = await api.post<ApiResponse<import('../types').ContractDocument>>(`/contracts/${contractId}/documents`, formData, {
|
||||||
|
headers: { 'Content-Type': 'multipart/form-data' },
|
||||||
|
});
|
||||||
|
return res.data;
|
||||||
|
},
|
||||||
|
deleteDocument: async (contractId: number, documentId: number) => {
|
||||||
|
const res = await api.delete<ApiResponse<void>>(`/contracts/${contractId}/documents/${documentId}`);
|
||||||
|
return res.data;
|
||||||
|
},
|
||||||
// Folgezähler
|
// Folgezähler
|
||||||
addSuccessorMeter: async (contractId: number, data: { meterId: number; installedAt?: string; finalReadingPrevious?: number }) => {
|
addSuccessorMeter: async (contractId: number, data: { meterId: number; installedAt?: string; finalReadingPrevious?: number }) => {
|
||||||
const res = await api.post<ApiResponse<any>>(`/contracts/${contractId}/successor-meter`, data);
|
const res = await api.post<ApiResponse<any>>(`/contracts/${contractId}/successor-meter`, data);
|
||||||
|
|
|
||||||
|
|
@ -152,6 +152,17 @@ export interface IdentityDocument {
|
||||||
licenseIssueDate?: string;
|
licenseIssueDate?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ContractDocument {
|
||||||
|
id: number;
|
||||||
|
contractId: number;
|
||||||
|
documentType: string;
|
||||||
|
documentPath: string;
|
||||||
|
originalName: string;
|
||||||
|
notes?: string;
|
||||||
|
uploadedBy?: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
export type MeterTariffModel = 'SINGLE' | 'DUAL';
|
export type MeterTariffModel = 'SINGLE' | 'DUAL';
|
||||||
|
|
||||||
export interface Meter {
|
export interface Meter {
|
||||||
|
|
@ -391,6 +402,8 @@ export interface Contract {
|
||||||
mobileDetails?: MobileContractDetails;
|
mobileDetails?: MobileContractDetails;
|
||||||
tvDetails?: TvContractDetails;
|
tvDetails?: TvContractDetails;
|
||||||
carInsuranceDetails?: CarInsuranceDetails;
|
carInsuranceDetails?: CarInsuranceDetails;
|
||||||
|
invoices?: Invoice[];
|
||||||
|
documents?: ContractDocument[];
|
||||||
// Snooze: Vertrag zurückstellen
|
// Snooze: Vertrag zurückstellen
|
||||||
nextReviewDate?: string;
|
nextReviewDate?: string;
|
||||||
followUpContract?: {
|
followUpContract?: {
|
||||||
|
|
@ -431,6 +444,10 @@ export interface InternetContractDetails {
|
||||||
// Internet-Zugangsdaten
|
// Internet-Zugangsdaten
|
||||||
internetUsername?: string;
|
internetUsername?: string;
|
||||||
internetPasswordEncrypted?: string;
|
internetPasswordEncrypted?: string;
|
||||||
|
// Objekt & Lage
|
||||||
|
propertyType?: string;
|
||||||
|
propertyLocation?: string;
|
||||||
|
connectionLocation?: string;
|
||||||
// Glasfaser-spezifisch
|
// Glasfaser-spezifisch
|
||||||
homeId?: string;
|
homeId?: string;
|
||||||
// Vodafone DSL/Kabel spezifisch
|
// Vodafone DSL/Kabel spezifisch
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue