snooze vor expired, contracts, display snoozed contracts if an item is missing, un snooze implemented, fixed invoice upload bug

This commit is contained in:
2026-02-08 13:08:58 +01:00
parent 3a9fcc5ec9
commit efe8ac25cb
39 changed files with 1369 additions and 800 deletions
+1
View File
@@ -11,4 +11,5 @@ export declare function getSimCardCredentials(req: Request, res: Response): Prom
export declare function getInternetCredentials(req: Request, res: Response): Promise<void>;
export declare function getSipCredentials(req: Request, res: Response): Promise<void>;
export declare function getCockpit(req: AuthRequest, res: Response): Promise<void>;
export declare function snoozeContract(req: Request, res: Response): Promise<void>;
//# sourceMappingURL=contract.controller.d.ts.map
+1 -1
View File
@@ -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;AAG5C,OAAO,EAAe,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAE7D,wBAAsB,YAAY,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAwCjF;AAED,wBAAsB,WAAW,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CA8BhF;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAU/E;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAU/E;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAU/E;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAU/E;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"}
{"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;AAI5C,OAAO,EAAe,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAI7D,wBAAsB,YAAY,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAwCjF;AAED,wBAAsB,WAAW,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CA8BhF;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAU/E;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAU/E;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAU/E;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAU/E;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,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAyC/E"}
+44
View File
@@ -44,8 +44,11 @@ exports.getSimCardCredentials = getSimCardCredentials;
exports.getInternetCredentials = getInternetCredentials;
exports.getSipCredentials = getSipCredentials;
exports.getCockpit = getCockpit;
exports.snoozeContract = snoozeContract;
const client_1 = require("@prisma/client");
const contractService = __importStar(require("../services/contract.service.js"));
const contractCockpitService = __importStar(require("../services/contractCockpit.service.js"));
const prisma = new client_1.PrismaClient();
async function getContracts(req, res) {
try {
const { customerId, type, status, search, page, limit, tree } = req.query;
@@ -230,4 +233,45 @@ async function getCockpit(req, res) {
});
}
}
// ==================== SNOOZE (VERTRAG ZURÜCKSTELLEN) ====================
async function snoozeContract(req, res) {
try {
const id = parseInt(req.params.id);
const { nextReviewDate, months } = req.body;
let reviewDate = null;
if (nextReviewDate) {
// Explizites Datum angegeben
reviewDate = new Date(nextReviewDate);
}
else if (months) {
// Monate angegeben → berechne Datum
reviewDate = new Date();
reviewDate.setMonth(reviewDate.getMonth() + months);
}
// Wenn beides leer → nextReviewDate wird auf null gesetzt (Snooze aufheben)
const updated = await prisma.contract.update({
where: { id },
data: { nextReviewDate: reviewDate },
select: {
id: true,
contractNumber: true,
nextReviewDate: true,
},
});
res.json({
success: true,
data: updated,
message: reviewDate
? `Vertrag zurückgestellt bis ${reviewDate.toLocaleDateString('de-DE')}`
: 'Zurückstellung aufgehoben',
});
}
catch (error) {
console.error('Snooze error:', error);
res.status(500).json({
success: false,
error: 'Fehler beim Zurückstellen des Vertrags',
});
}
}
//# sourceMappingURL=contract.controller.js.map
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"contract.routes.d.ts","sourceRoot":"","sources":["../../src/routes/contract.routes.ts"],"names":[],"mappings":"AAIA,QAAA,MAAM,MAAM,4CAAW,CAAC;AA2BxB,eAAe,MAAM,CAAC"}
{"version":3,"file":"contract.routes.d.ts","sourceRoot":"","sources":["../../src/routes/contract.routes.ts"],"names":[],"mappings":"AAIA,QAAA,MAAM,MAAM,4CAAW,CAAC;AA8BxB,eAAe,MAAM,CAAC"}
+2
View File
@@ -46,6 +46,8 @@ router.put('/:id', auth_js_1.authenticate, (0, auth_js_1.requirePermission)('con
router.delete('/:id', auth_js_1.authenticate, (0, auth_js_1.requirePermission)('contracts:delete'), contractController.deleteContract);
// Follow-up contract
router.post('/:id/follow-up', auth_js_1.authenticate, (0, auth_js_1.requirePermission)('contracts:create'), contractController.createFollowUp);
// Snooze (Vertrag zurückstellen)
router.patch('/:id/snooze', auth_js_1.authenticate, (0, auth_js_1.requirePermission)('contracts:update'), contractController.snoozeContract);
// Get decrypted password
router.get('/:id/password', auth_js_1.authenticate, (0, auth_js_1.requirePermission)('contracts:read'), contractController.getContractPassword);
// Get decrypted SimCard PIN/PUK
+1 -1
View File
@@ -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,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,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,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"}
+8
View File
@@ -135,6 +135,7 @@ export declare function getAllContracts(filters: ContractFilters): Promise<{
wasSpecialCancellation: boolean;
portalUsername: string | null;
stressfreiEmailId: number | null;
nextReviewDate: Date | null;
})[];
pagination: {
page: number;
@@ -564,6 +565,7 @@ export declare function getContractById(id: number, decryptPassword?: boolean):
wasSpecialCancellation: boolean;
portalUsername: string | null;
stressfreiEmailId: number | null;
nextReviewDate: Date | null;
}) | null;
followUpContract: {
id: number;
@@ -609,6 +611,7 @@ export declare function getContractById(id: number, decryptPassword?: boolean):
wasSpecialCancellation: boolean;
portalUsername: string | null;
stressfreiEmailId: number | null;
nextReviewDate: Date | null;
}) | null>;
interface ContractCreateData {
customerId: number;
@@ -894,6 +897,7 @@ export declare function createContract(data: ContractCreateData): Promise<{
wasSpecialCancellation: boolean;
portalUsername: string | null;
stressfreiEmailId: number | null;
nextReviewDate: Date | null;
}>;
export declare function updateContract(id: number, data: Partial<ContractCreateData>): Promise<({
customer: {
@@ -1316,6 +1320,7 @@ export declare function updateContract(id: number, data: Partial<ContractCreateD
wasSpecialCancellation: boolean;
portalUsername: string | null;
stressfreiEmailId: number | null;
nextReviewDate: Date | null;
}) | null;
followUpContract: {
id: number;
@@ -1361,6 +1366,7 @@ export declare function updateContract(id: number, data: Partial<ContractCreateD
wasSpecialCancellation: boolean;
portalUsername: string | null;
stressfreiEmailId: number | null;
nextReviewDate: Date | null;
}) | null>;
export declare function deleteContract(id: number): Promise<{
id: number;
@@ -1401,6 +1407,7 @@ export declare function deleteContract(id: number): Promise<{
wasSpecialCancellation: boolean;
portalUsername: string | null;
stressfreiEmailId: number | null;
nextReviewDate: Date | null;
}>;
export declare function createFollowUpContract(previousContractId: number): Promise<{
customer: {
@@ -1588,6 +1595,7 @@ export declare function createFollowUpContract(previousContractId: number): Prom
wasSpecialCancellation: boolean;
portalUsername: string | null;
stressfreiEmailId: number | null;
nextReviewDate: Date | null;
}>;
export declare function getContractPassword(id: number): Promise<string | null>;
export declare function getSimCardCredentials(simCardId: number): Promise<{
File diff suppressed because one or more lines are too long
+1
View File
@@ -43,6 +43,7 @@ export interface CockpitSummary {
missingInvoices: number;
openTasks: number;
pendingContracts: number;
reviewDue: number;
};
}
export interface CockpitResult {
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"contractCockpit.service.d.ts","sourceRoot":"","sources":["../../src/services/contractCockpit.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,cAAc,EACpC,MAAM,gBAAgB,CAAC;AAMxB,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,CAAC;AAElE,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,YAAY,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,cAAc,CAAC;IACvB,QAAQ,EAAE;QACR,EAAE,EAAE,MAAM,CAAC;QACX,cAAc,EAAE,MAAM,CAAC;QACvB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,QAAQ,CAAC,EAAE;QACT,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,MAAM,CAAC,EAAE;QACP,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,cAAc,EAAE,YAAY,CAAC;CAC9B;AAED,MAAM,WAAW,cAAc;IAC7B,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE;QACV,qBAAqB,EAAE,MAAM,CAAC;QAC9B,cAAc,EAAE,MAAM,CAAC;QACvB,kBAAkB,EAAE,MAAM,CAAC;QAC3B,WAAW,EAAE,MAAM,CAAC;QACpB,eAAe,EAAE,MAAM,CAAC;QACxB,SAAS,EAAE,MAAM,CAAC;QAClB,gBAAgB,EAAE,MAAM,CAAC;KAC1B,CAAC;CACH;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,eAAe,EAAE,CAAC;IAC7B,OAAO,EAAE,cAAc,CAAC;IACxB,UAAU,EAAE;QACV,YAAY,EAAE,MAAM,CAAC;QACrB,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAyED,wBAAsB,cAAc,IAAI,OAAO,CAAC,aAAa,CAAC,CAya7D"}
{"version":3,"file":"contractCockpit.service.d.ts","sourceRoot":"","sources":["../../src/services/contractCockpit.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,cAAc,EACpC,MAAM,gBAAgB,CAAC;AAMxB,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,CAAC;AAElE,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,YAAY,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,cAAc,CAAC;IACvB,QAAQ,EAAE;QACR,EAAE,EAAE,MAAM,CAAC;QACX,cAAc,EAAE,MAAM,CAAC;QACvB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,QAAQ,CAAC,EAAE;QACT,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,MAAM,CAAC,EAAE;QACP,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,cAAc,EAAE,YAAY,CAAC;CAC9B;AAED,MAAM,WAAW,cAAc;IAC7B,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE;QACV,qBAAqB,EAAE,MAAM,CAAC;QAC9B,cAAc,EAAE,MAAM,CAAC;QACvB,kBAAkB,EAAE,MAAM,CAAC;QAC3B,WAAW,EAAE,MAAM,CAAC;QACpB,eAAe,EAAE,MAAM,CAAC;QACxB,SAAS,EAAE,MAAM,CAAC;QAClB,gBAAgB,EAAE,MAAM,CAAC;QACzB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,eAAe,EAAE,CAAC;IAC7B,OAAO,EAAE,cAAc,CAAC;IACxB,UAAU,EAAE;QACV,YAAY,EAAE,MAAM,CAAC;QACrB,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAyED,wBAAsB,cAAc,IAAI,OAAO,CAAC,aAAa,CAAC,CAyc7D"}
+34 -3
View File
@@ -190,14 +190,42 @@ async function getCockpitData() {
missingInvoices: 0,
openTasks: 0,
pendingContracts: 0,
reviewDue: 0,
},
};
for (const contract of contracts) {
const issues = [];
// SNOOZE-LOGIK: Prüfen ob Snooze aktiv ist (für Fristen-Unterdrückung)
let snoozeActive = false;
if (contract.nextReviewDate) {
const reviewDate = new Date(contract.nextReviewDate);
const now = new Date();
now.setHours(0, 0, 0, 0);
reviewDate.setHours(0, 0, 0, 0);
if (reviewDate > now) {
// Snooze aktiv → NUR Fristen-Warnungen unterdrücken, andere Prüfungen laufen weiter
snoozeActive = true;
}
else {
// Snooze abgelaufen → "Erneute Prüfung fällig" Warnung
const daysSince = Math.floor((now.getTime() - reviewDate.getTime()) / (1000 * 60 * 60 * 24));
issues.push({
type: 'review_due',
label: 'Erneute Prüfung fällig',
urgency: daysSince > 30 ? 'critical' : 'warning',
daysRemaining: -daysSince,
details: daysSince === 0
? 'Heute zur Prüfung fällig'
: `Zur Prüfung seit ${daysSince} Tagen fällig`,
});
summary.byCategory.reviewDue++;
}
}
// Prüfen ob aktiver Folgevertrag existiert - dann keine Kündigungswarnungen nötig
const hasActiveFollowUp = contract.followUpContract?.status === 'ACTIVE';
// 1. KÜNDIGUNGSFRIST (nur wenn kein aktiver Folgevertrag)
if (!hasActiveFollowUp) {
// 1. KÜNDIGUNGSFRIST (nur wenn kein aktiver Folgevertrag UND Snooze nicht aktiv)
// Snooze unterdrückt NUR Fristen-bezogene Warnungen!
if (!hasActiveFollowUp && !snoozeActive) {
const cancellationDeadline = calculateCancellationDeadline(contract.endDate, contract.cancellationPeriod?.code);
const daysToCancellation = daysUntil(cancellationDeadline);
if (daysToCancellation !== null && daysToCancellation <= okDays) {
@@ -236,7 +264,9 @@ async function getCockpitData() {
}
}
}
// 2. VERTRAGSENDE
// 2. VERTRAGSENDE (nur wenn Snooze nicht aktiv)
// Snooze unterdrückt NUR Fristen-bezogene Warnungen!
if (!snoozeActive) {
const daysToEnd = daysUntil(contract.endDate);
if (daysToEnd !== null && daysToEnd <= okDays) {
const urgency = getUrgencyByDays(daysToEnd, criticalDays, warningDays, okDays);
@@ -253,6 +283,7 @@ async function getCockpitData() {
summary.byCategory.contractEnding++;
}
}
}
// 3. FEHLENDE PORTAL-ZUGANGSDATEN
// Benutzername kann entweder manuell (portalUsername) oder via Stressfrei-Wechseln E-Mail (stressfreiEmailId) gesetzt sein
const hasUsername = contract.portalUsername || contract.stressfreiEmailId;
File diff suppressed because one or more lines are too long
+1
View File
@@ -196,6 +196,7 @@ export declare function getCustomerById(id: number): Promise<({
wasSpecialCancellation: boolean;
portalUsername: string | null;
stressfreiEmailId: number | null;
nextReviewDate: Date | null;
})[];
} & {
id: number;
File diff suppressed because one or more lines are too long
+5 -3
View File
File diff suppressed because one or more lines are too long
+1
View File
@@ -433,6 +433,7 @@ exports.Prisma.ContractScalarFieldEnum = {
portalUsername: 'portalUsername',
portalPasswordEncrypted: 'portalPasswordEncrypted',
stressfreiEmailId: 'stressfreiEmailId',
nextReviewDate: 'nextReviewDate',
notes: 'notes',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
+136
View File
@@ -26320,6 +26320,7 @@ export namespace Prisma {
portalUsername: string | null
portalPasswordEncrypted: string | null
stressfreiEmailId: number | null
nextReviewDate: Date | null
notes: string | null
createdAt: Date | null
updatedAt: Date | null
@@ -26361,6 +26362,7 @@ export namespace Prisma {
portalUsername: string | null
portalPasswordEncrypted: string | null
stressfreiEmailId: number | null
nextReviewDate: Date | null
notes: string | null
createdAt: Date | null
updatedAt: Date | null
@@ -26402,6 +26404,7 @@ export namespace Prisma {
portalUsername: number
portalPasswordEncrypted: number
stressfreiEmailId: number
nextReviewDate: number
notes: number
createdAt: number
updatedAt: number
@@ -26481,6 +26484,7 @@ export namespace Prisma {
portalUsername?: true
portalPasswordEncrypted?: true
stressfreiEmailId?: true
nextReviewDate?: true
notes?: true
createdAt?: true
updatedAt?: true
@@ -26522,6 +26526,7 @@ export namespace Prisma {
portalUsername?: true
portalPasswordEncrypted?: true
stressfreiEmailId?: true
nextReviewDate?: true
notes?: true
createdAt?: true
updatedAt?: true
@@ -26563,6 +26568,7 @@ export namespace Prisma {
portalUsername?: true
portalPasswordEncrypted?: true
stressfreiEmailId?: true
nextReviewDate?: true
notes?: true
createdAt?: true
updatedAt?: true
@@ -26691,6 +26697,7 @@ export namespace Prisma {
portalUsername: string | null
portalPasswordEncrypted: string | null
stressfreiEmailId: number | null
nextReviewDate: Date | null
notes: string | null
createdAt: Date
updatedAt: Date
@@ -26751,6 +26758,7 @@ export namespace Prisma {
portalUsername?: boolean
portalPasswordEncrypted?: boolean
stressfreiEmailId?: boolean
nextReviewDate?: boolean
notes?: boolean
createdAt?: boolean
updatedAt?: boolean
@@ -26815,6 +26823,7 @@ export namespace Prisma {
portalUsername?: boolean
portalPasswordEncrypted?: boolean
stressfreiEmailId?: boolean
nextReviewDate?: boolean
notes?: boolean
createdAt?: boolean
updatedAt?: boolean
@@ -26906,6 +26915,7 @@ export namespace Prisma {
portalUsername: string | null
portalPasswordEncrypted: string | null
stressfreiEmailId: number | null
nextReviewDate: Date | null
notes: string | null
createdAt: Date
updatedAt: Date
@@ -27334,6 +27344,7 @@ export namespace Prisma {
readonly portalUsername: FieldRef<"Contract", 'String'>
readonly portalPasswordEncrypted: FieldRef<"Contract", 'String'>
readonly stressfreiEmailId: FieldRef<"Contract", 'Int'>
readonly nextReviewDate: FieldRef<"Contract", 'DateTime'>
readonly notes: FieldRef<"Contract", 'String'>
readonly createdAt: FieldRef<"Contract", 'DateTime'>
readonly updatedAt: FieldRef<"Contract", 'DateTime'>
@@ -38180,6 +38191,7 @@ export namespace Prisma {
portalUsername: 'portalUsername',
portalPasswordEncrypted: 'portalPasswordEncrypted',
stressfreiEmailId: 'stressfreiEmailId',
nextReviewDate: 'nextReviewDate',
notes: 'notes',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
@@ -40345,6 +40357,7 @@ export namespace Prisma {
portalUsername?: StringNullableFilter<"Contract"> | string | null
portalPasswordEncrypted?: StringNullableFilter<"Contract"> | string | null
stressfreiEmailId?: IntNullableFilter<"Contract"> | number | null
nextReviewDate?: DateTimeNullableFilter<"Contract"> | Date | string | null
notes?: StringNullableFilter<"Contract"> | string | null
createdAt?: DateTimeFilter<"Contract"> | Date | string
updatedAt?: DateTimeFilter<"Contract"> | Date | string
@@ -40407,6 +40420,7 @@ export namespace Prisma {
portalUsername?: SortOrderInput | SortOrder
portalPasswordEncrypted?: SortOrderInput | SortOrder
stressfreiEmailId?: SortOrderInput | SortOrder
nextReviewDate?: SortOrderInput | SortOrder
notes?: SortOrderInput | SortOrder
createdAt?: SortOrder
updatedAt?: SortOrder
@@ -40472,6 +40486,7 @@ export namespace Prisma {
portalUsername?: StringNullableFilter<"Contract"> | string | null
portalPasswordEncrypted?: StringNullableFilter<"Contract"> | string | null
stressfreiEmailId?: IntNullableFilter<"Contract"> | number | null
nextReviewDate?: DateTimeNullableFilter<"Contract"> | Date | string | null
notes?: StringNullableFilter<"Contract"> | string | null
createdAt?: DateTimeFilter<"Contract"> | Date | string
updatedAt?: DateTimeFilter<"Contract"> | Date | string
@@ -40534,6 +40549,7 @@ export namespace Prisma {
portalUsername?: SortOrderInput | SortOrder
portalPasswordEncrypted?: SortOrderInput | SortOrder
stressfreiEmailId?: SortOrderInput | SortOrder
nextReviewDate?: SortOrderInput | SortOrder
notes?: SortOrderInput | SortOrder
createdAt?: SortOrder
updatedAt?: SortOrder
@@ -40583,6 +40599,7 @@ export namespace Prisma {
portalUsername?: StringNullableWithAggregatesFilter<"Contract"> | string | null
portalPasswordEncrypted?: StringNullableWithAggregatesFilter<"Contract"> | string | null
stressfreiEmailId?: IntNullableWithAggregatesFilter<"Contract"> | number | null
nextReviewDate?: DateTimeNullableWithAggregatesFilter<"Contract"> | Date | string | null
notes?: StringNullableWithAggregatesFilter<"Contract"> | string | null
createdAt?: DateTimeWithAggregatesFilter<"Contract"> | Date | string
updatedAt?: DateTimeWithAggregatesFilter<"Contract"> | Date | string
@@ -43378,6 +43395,7 @@ export namespace Prisma {
wasSpecialCancellation?: boolean
portalUsername?: string | null
portalPasswordEncrypted?: string | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -43440,6 +43458,7 @@ export namespace Prisma {
portalUsername?: string | null
portalPasswordEncrypted?: string | null
stressfreiEmailId?: number | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -43475,6 +43494,7 @@ export namespace Prisma {
wasSpecialCancellation?: BoolFieldUpdateOperationsInput | boolean
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -43537,6 +43557,7 @@ export namespace Prisma {
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
stressfreiEmailId?: NullableIntFieldUpdateOperationsInput | number | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -43586,6 +43607,7 @@ export namespace Prisma {
portalUsername?: string | null
portalPasswordEncrypted?: string | null
stressfreiEmailId?: number | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -43613,6 +43635,7 @@ export namespace Prisma {
wasSpecialCancellation?: BoolFieldUpdateOperationsInput | boolean
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -43654,6 +43677,7 @@ export namespace Prisma {
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
stressfreiEmailId?: NullableIntFieldUpdateOperationsInput | number | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -46230,6 +46254,7 @@ export namespace Prisma {
portalUsername?: SortOrder
portalPasswordEncrypted?: SortOrder
stressfreiEmailId?: SortOrder
nextReviewDate?: SortOrder
notes?: SortOrder
createdAt?: SortOrder
updatedAt?: SortOrder
@@ -46289,6 +46314,7 @@ export namespace Prisma {
portalUsername?: SortOrder
portalPasswordEncrypted?: SortOrder
stressfreiEmailId?: SortOrder
nextReviewDate?: SortOrder
notes?: SortOrder
createdAt?: SortOrder
updatedAt?: SortOrder
@@ -46330,6 +46356,7 @@ export namespace Prisma {
portalUsername?: SortOrder
portalPasswordEncrypted?: SortOrder
stressfreiEmailId?: SortOrder
nextReviewDate?: SortOrder
notes?: SortOrder
createdAt?: SortOrder
updatedAt?: SortOrder
@@ -50508,6 +50535,7 @@ export namespace Prisma {
wasSpecialCancellation?: boolean
portalUsername?: string | null
portalPasswordEncrypted?: string | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -50568,6 +50596,7 @@ export namespace Prisma {
portalUsername?: string | null
portalPasswordEncrypted?: string | null
stressfreiEmailId?: number | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -50902,6 +50931,7 @@ export namespace Prisma {
portalUsername?: StringNullableFilter<"Contract"> | string | null
portalPasswordEncrypted?: StringNullableFilter<"Contract"> | string | null
stressfreiEmailId?: IntNullableFilter<"Contract"> | number | null
nextReviewDate?: DateTimeNullableFilter<"Contract"> | Date | string | null
notes?: StringNullableFilter<"Contract"> | string | null
createdAt?: DateTimeFilter<"Contract"> | Date | string
updatedAt?: DateTimeFilter<"Contract"> | Date | string
@@ -51376,6 +51406,7 @@ export namespace Prisma {
wasSpecialCancellation?: boolean
portalUsername?: string | null
portalPasswordEncrypted?: string | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -51436,6 +51467,7 @@ export namespace Prisma {
portalUsername?: string | null
portalPasswordEncrypted?: string | null
stressfreiEmailId?: number | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -51481,6 +51513,7 @@ export namespace Prisma {
wasSpecialCancellation?: boolean
portalUsername?: string | null
portalPasswordEncrypted?: string | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -51541,6 +51574,7 @@ export namespace Prisma {
portalUsername?: string | null
portalPasswordEncrypted?: string | null
stressfreiEmailId?: number | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -51780,6 +51814,7 @@ export namespace Prisma {
wasSpecialCancellation?: boolean
portalUsername?: string | null
portalPasswordEncrypted?: string | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -51840,6 +51875,7 @@ export namespace Prisma {
portalUsername?: string | null
portalPasswordEncrypted?: string | null
stressfreiEmailId?: number | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -52063,6 +52099,7 @@ export namespace Prisma {
wasSpecialCancellation?: boolean
portalUsername?: string | null
portalPasswordEncrypted?: string | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -52123,6 +52160,7 @@ export namespace Prisma {
portalUsername?: string | null
portalPasswordEncrypted?: string | null
stressfreiEmailId?: number | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -52346,6 +52384,7 @@ export namespace Prisma {
wasSpecialCancellation?: boolean
portalUsername?: string | null
portalPasswordEncrypted?: string | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -52406,6 +52445,7 @@ export namespace Prisma {
wasSpecialCancellation?: boolean
portalUsername?: string | null
portalPasswordEncrypted?: string | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -52699,6 +52739,7 @@ export namespace Prisma {
wasSpecialCancellation?: boolean
portalUsername?: string | null
portalPasswordEncrypted?: string | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -52760,6 +52801,7 @@ export namespace Prisma {
portalUsername?: string | null
portalPasswordEncrypted?: string | null
stressfreiEmailId?: number | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -52854,6 +52896,7 @@ export namespace Prisma {
wasSpecialCancellation?: BoolFieldUpdateOperationsInput | boolean
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -52915,6 +52958,7 @@ export namespace Prisma {
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
stressfreiEmailId?: NullableIntFieldUpdateOperationsInput | number | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -53299,6 +53343,7 @@ export namespace Prisma {
wasSpecialCancellation?: boolean
portalUsername?: string | null
portalPasswordEncrypted?: string | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -53359,6 +53404,7 @@ export namespace Prisma {
portalUsername?: string | null
portalPasswordEncrypted?: string | null
stressfreiEmailId?: number | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -53420,6 +53466,7 @@ export namespace Prisma {
wasSpecialCancellation?: boolean
portalUsername?: string | null
portalPasswordEncrypted?: string | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -53480,6 +53527,7 @@ export namespace Prisma {
portalUsername?: string | null
portalPasswordEncrypted?: string | null
stressfreiEmailId?: number | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -53541,6 +53589,7 @@ export namespace Prisma {
wasSpecialCancellation?: boolean
portalUsername?: string | null
portalPasswordEncrypted?: string | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -53601,6 +53650,7 @@ export namespace Prisma {
portalUsername?: string | null
portalPasswordEncrypted?: string | null
stressfreiEmailId?: number | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -53689,6 +53739,7 @@ export namespace Prisma {
wasSpecialCancellation?: boolean
portalUsername?: string | null
portalPasswordEncrypted?: string | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -53749,6 +53800,7 @@ export namespace Prisma {
portalUsername?: string | null
portalPasswordEncrypted?: string | null
stressfreiEmailId?: number | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -53866,6 +53918,7 @@ export namespace Prisma {
wasSpecialCancellation?: boolean
portalUsername?: string | null
portalPasswordEncrypted?: string | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -53926,6 +53979,7 @@ export namespace Prisma {
portalUsername?: string | null
portalPasswordEncrypted?: string | null
stressfreiEmailId?: number | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -54021,6 +54075,7 @@ export namespace Prisma {
wasSpecialCancellation?: boolean
portalUsername?: string | null
portalPasswordEncrypted?: string | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -54081,6 +54136,7 @@ export namespace Prisma {
portalUsername?: string | null
portalPasswordEncrypted?: string | null
stressfreiEmailId?: number | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -54450,6 +54506,7 @@ export namespace Prisma {
wasSpecialCancellation?: boolean
portalUsername?: string | null
portalPasswordEncrypted?: string | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -54511,6 +54568,7 @@ export namespace Prisma {
portalUsername?: string | null
portalPasswordEncrypted?: string | null
stressfreiEmailId?: number | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -54550,6 +54608,7 @@ export namespace Prisma {
wasSpecialCancellation?: boolean
portalUsername?: string | null
portalPasswordEncrypted?: string | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -54610,6 +54669,7 @@ export namespace Prisma {
portalUsername?: string | null
portalPasswordEncrypted?: string | null
stressfreiEmailId?: number | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -55357,6 +55417,7 @@ export namespace Prisma {
wasSpecialCancellation?: BoolFieldUpdateOperationsInput | boolean
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -55418,6 +55479,7 @@ export namespace Prisma {
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
stressfreiEmailId?: NullableIntFieldUpdateOperationsInput | number | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -55463,6 +55525,7 @@ export namespace Prisma {
wasSpecialCancellation?: BoolFieldUpdateOperationsInput | boolean
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -55523,6 +55586,7 @@ export namespace Prisma {
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
stressfreiEmailId?: NullableIntFieldUpdateOperationsInput | number | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -55890,6 +55954,7 @@ export namespace Prisma {
wasSpecialCancellation?: boolean
portalUsername?: string | null
portalPasswordEncrypted?: string | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -55951,6 +56016,7 @@ export namespace Prisma {
portalUsername?: string | null
portalPasswordEncrypted?: string | null
stressfreiEmailId?: number | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -56030,6 +56096,7 @@ export namespace Prisma {
wasSpecialCancellation?: BoolFieldUpdateOperationsInput | boolean
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -56091,6 +56158,7 @@ export namespace Prisma {
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
stressfreiEmailId?: NullableIntFieldUpdateOperationsInput | number | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -56221,6 +56289,7 @@ export namespace Prisma {
wasSpecialCancellation?: boolean
portalUsername?: string | null
portalPasswordEncrypted?: string | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -56282,6 +56351,7 @@ export namespace Prisma {
portalUsername?: string | null
portalPasswordEncrypted?: string | null
stressfreiEmailId?: number | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -56389,6 +56459,7 @@ export namespace Prisma {
wasSpecialCancellation?: BoolFieldUpdateOperationsInput | boolean
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -56450,6 +56521,7 @@ export namespace Prisma {
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
stressfreiEmailId?: NullableIntFieldUpdateOperationsInput | number | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -56618,6 +56690,7 @@ export namespace Prisma {
wasSpecialCancellation?: boolean
portalUsername?: string | null
portalPasswordEncrypted?: string | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -56679,6 +56752,7 @@ export namespace Prisma {
portalUsername?: string | null
portalPasswordEncrypted?: string | null
stressfreiEmailId?: number | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -56756,6 +56830,7 @@ export namespace Prisma {
wasSpecialCancellation?: BoolFieldUpdateOperationsInput | boolean
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -56817,6 +56892,7 @@ export namespace Prisma {
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
stressfreiEmailId?: NullableIntFieldUpdateOperationsInput | number | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -56950,6 +57026,7 @@ export namespace Prisma {
wasSpecialCancellation?: boolean
portalUsername?: string | null
portalPasswordEncrypted?: string | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -57011,6 +57088,7 @@ export namespace Prisma {
portalUsername?: string | null
portalPasswordEncrypted?: string | null
stressfreiEmailId?: number | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -57094,6 +57172,7 @@ export namespace Prisma {
wasSpecialCancellation?: BoolFieldUpdateOperationsInput | boolean
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -57155,6 +57234,7 @@ export namespace Prisma {
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
stressfreiEmailId?: NullableIntFieldUpdateOperationsInput | number | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -57287,6 +57367,7 @@ export namespace Prisma {
wasSpecialCancellation?: boolean
portalUsername?: string | null
portalPasswordEncrypted?: string | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -57348,6 +57429,7 @@ export namespace Prisma {
portalUsername?: string | null
portalPasswordEncrypted?: string | null
stressfreiEmailId?: number | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -57398,6 +57480,7 @@ export namespace Prisma {
wasSpecialCancellation?: BoolFieldUpdateOperationsInput | boolean
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -57459,6 +57542,7 @@ export namespace Prisma {
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
stressfreiEmailId?: NullableIntFieldUpdateOperationsInput | number | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -57493,6 +57577,7 @@ export namespace Prisma {
wasSpecialCancellation?: boolean
portalUsername?: string | null
portalPasswordEncrypted?: string | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -57554,6 +57639,7 @@ export namespace Prisma {
portalUsername?: string | null
portalPasswordEncrypted?: string | null
stressfreiEmailId?: number | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -57604,6 +57690,7 @@ export namespace Prisma {
wasSpecialCancellation?: BoolFieldUpdateOperationsInput | boolean
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -57665,6 +57752,7 @@ export namespace Prisma {
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
stressfreiEmailId?: NullableIntFieldUpdateOperationsInput | number | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -57842,6 +57930,7 @@ export namespace Prisma {
portalUsername?: string | null
portalPasswordEncrypted?: string | null
stressfreiEmailId?: number | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -58096,6 +58185,7 @@ export namespace Prisma {
wasSpecialCancellation?: BoolFieldUpdateOperationsInput | boolean
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -58156,6 +58246,7 @@ export namespace Prisma {
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
stressfreiEmailId?: NullableIntFieldUpdateOperationsInput | number | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -58204,6 +58295,7 @@ export namespace Prisma {
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
stressfreiEmailId?: NullableIntFieldUpdateOperationsInput | number | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -58296,6 +58388,7 @@ export namespace Prisma {
portalUsername?: string | null
portalPasswordEncrypted?: string | null
stressfreiEmailId?: number | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -58336,6 +58429,7 @@ export namespace Prisma {
portalUsername?: string | null
portalPasswordEncrypted?: string | null
stressfreiEmailId?: number | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -58363,6 +58457,7 @@ export namespace Prisma {
wasSpecialCancellation?: BoolFieldUpdateOperationsInput | boolean
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -58423,6 +58518,7 @@ export namespace Prisma {
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
stressfreiEmailId?: NullableIntFieldUpdateOperationsInput | number | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -58471,6 +58567,7 @@ export namespace Prisma {
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
stressfreiEmailId?: NullableIntFieldUpdateOperationsInput | number | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -58498,6 +58595,7 @@ export namespace Prisma {
wasSpecialCancellation?: BoolFieldUpdateOperationsInput | boolean
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -58558,6 +58656,7 @@ export namespace Prisma {
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
stressfreiEmailId?: NullableIntFieldUpdateOperationsInput | number | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -58606,6 +58705,7 @@ export namespace Prisma {
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
stressfreiEmailId?: NullableIntFieldUpdateOperationsInput | number | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -58646,6 +58746,7 @@ export namespace Prisma {
portalUsername?: string | null
portalPasswordEncrypted?: string | null
stressfreiEmailId?: number | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -58673,6 +58774,7 @@ export namespace Prisma {
wasSpecialCancellation?: BoolFieldUpdateOperationsInput | boolean
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -58733,6 +58835,7 @@ export namespace Prisma {
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
stressfreiEmailId?: NullableIntFieldUpdateOperationsInput | number | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -58781,6 +58884,7 @@ export namespace Prisma {
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
stressfreiEmailId?: NullableIntFieldUpdateOperationsInput | number | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -58821,6 +58925,7 @@ export namespace Prisma {
portalUsername?: string | null
portalPasswordEncrypted?: string | null
stressfreiEmailId?: number | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -58848,6 +58953,7 @@ export namespace Prisma {
wasSpecialCancellation?: BoolFieldUpdateOperationsInput | boolean
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -58908,6 +59014,7 @@ export namespace Prisma {
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
stressfreiEmailId?: NullableIntFieldUpdateOperationsInput | number | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -58956,6 +59063,7 @@ export namespace Prisma {
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
stressfreiEmailId?: NullableIntFieldUpdateOperationsInput | number | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -58996,6 +59104,7 @@ export namespace Prisma {
wasSpecialCancellation?: boolean
portalUsername?: string | null
portalPasswordEncrypted?: string | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -59050,6 +59159,7 @@ export namespace Prisma {
wasSpecialCancellation?: BoolFieldUpdateOperationsInput | boolean
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -59110,6 +59220,7 @@ export namespace Prisma {
wasSpecialCancellation?: BoolFieldUpdateOperationsInput | boolean
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -59158,6 +59269,7 @@ export namespace Prisma {
wasSpecialCancellation?: BoolFieldUpdateOperationsInput | boolean
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -59366,6 +59478,7 @@ export namespace Prisma {
portalUsername?: string | null
portalPasswordEncrypted?: string | null
stressfreiEmailId?: number | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -59393,6 +59506,7 @@ export namespace Prisma {
wasSpecialCancellation?: BoolFieldUpdateOperationsInput | boolean
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -59453,6 +59567,7 @@ export namespace Prisma {
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
stressfreiEmailId?: NullableIntFieldUpdateOperationsInput | number | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -59501,6 +59616,7 @@ export namespace Prisma {
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
stressfreiEmailId?: NullableIntFieldUpdateOperationsInput | number | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -59541,6 +59657,7 @@ export namespace Prisma {
portalUsername?: string | null
portalPasswordEncrypted?: string | null
stressfreiEmailId?: number | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -59568,6 +59685,7 @@ export namespace Prisma {
wasSpecialCancellation?: BoolFieldUpdateOperationsInput | boolean
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -59628,6 +59746,7 @@ export namespace Prisma {
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
stressfreiEmailId?: NullableIntFieldUpdateOperationsInput | number | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -59676,6 +59795,7 @@ export namespace Prisma {
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
stressfreiEmailId?: NullableIntFieldUpdateOperationsInput | number | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -59716,6 +59836,7 @@ export namespace Prisma {
portalUsername?: string | null
portalPasswordEncrypted?: string | null
stressfreiEmailId?: number | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -59743,6 +59864,7 @@ export namespace Prisma {
wasSpecialCancellation?: BoolFieldUpdateOperationsInput | boolean
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -59803,6 +59925,7 @@ export namespace Prisma {
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
stressfreiEmailId?: NullableIntFieldUpdateOperationsInput | number | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -59851,6 +59974,7 @@ export namespace Prisma {
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
stressfreiEmailId?: NullableIntFieldUpdateOperationsInput | number | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -59899,6 +60023,7 @@ export namespace Prisma {
portalUsername?: string | null
portalPasswordEncrypted?: string | null
stressfreiEmailId?: number | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -59951,6 +60076,7 @@ export namespace Prisma {
wasSpecialCancellation?: BoolFieldUpdateOperationsInput | boolean
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -60011,6 +60137,7 @@ export namespace Prisma {
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
stressfreiEmailId?: NullableIntFieldUpdateOperationsInput | number | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -60059,6 +60186,7 @@ export namespace Prisma {
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
stressfreiEmailId?: NullableIntFieldUpdateOperationsInput | number | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -60099,6 +60227,7 @@ export namespace Prisma {
portalUsername?: string | null
portalPasswordEncrypted?: string | null
stressfreiEmailId?: number | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -60126,6 +60255,7 @@ export namespace Prisma {
wasSpecialCancellation?: BoolFieldUpdateOperationsInput | boolean
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -60186,6 +60316,7 @@ export namespace Prisma {
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
stressfreiEmailId?: NullableIntFieldUpdateOperationsInput | number | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -60234,6 +60365,7 @@ export namespace Prisma {
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
stressfreiEmailId?: NullableIntFieldUpdateOperationsInput | number | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -60274,6 +60406,7 @@ export namespace Prisma {
portalUsername?: string | null
portalPasswordEncrypted?: string | null
stressfreiEmailId?: number | null
nextReviewDate?: Date | string | null
notes?: string | null
createdAt?: Date | string
updatedAt?: Date | string
@@ -60301,6 +60434,7 @@ export namespace Prisma {
wasSpecialCancellation?: BoolFieldUpdateOperationsInput | boolean
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -60361,6 +60495,7 @@ export namespace Prisma {
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
stressfreiEmailId?: NullableIntFieldUpdateOperationsInput | number | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
@@ -60409,6 +60544,7 @@ export namespace Prisma {
portalUsername?: NullableStringFieldUpdateOperationsInput | string | null
portalPasswordEncrypted?: NullableStringFieldUpdateOperationsInput | string | null
stressfreiEmailId?: NullableIntFieldUpdateOperationsInput | number | null
nextReviewDate?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
notes?: NullableStringFieldUpdateOperationsInput | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
+5 -3
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,5 +1,5 @@
{
"name": "prisma-client-a41de88ab23bcfe6707e2a70a25cdd4736b5f2e024b64f8f8760b0e23260313f",
"name": "prisma-client-05071f627540060c098bd6f9ad2705f280d372ae8cdaf0576ff7e91f8d0f9863",
"main": "index.js",
"types": "index.d.ts",
"browser": "index-browser.js",
+3
View File
@@ -545,6 +545,9 @@ model Contract {
stressfreiEmailId Int?
stressfreiEmail StressfreiEmail? @relation(fields: [stressfreiEmailId], references: [id])
// Snooze: Vertrag zurückstellen bis Datum (für Cockpit)
nextReviewDate DateTime? // Erneute Prüfung am
notes String? @db.Text
energyDetails EnergyContractDetails?
+1
View File
@@ -433,6 +433,7 @@ exports.Prisma.ContractScalarFieldEnum = {
portalUsername: 'portalUsername',
portalPasswordEncrypted: 'portalPasswordEncrypted',
stressfreiEmailId: 'stressfreiEmailId',
nextReviewDate: 'nextReviewDate',
notes: 'notes',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE `Contract` ADD COLUMN `nextReviewDate` DATETIME(3) NULL;
+3
View File
@@ -545,6 +545,9 @@ model Contract {
stressfreiEmailId Int?
stressfreiEmail StressfreiEmail? @relation(fields: [stressfreiEmailId], references: [id])
// Snooze: Vertrag zurückstellen bis Datum (für Cockpit)
nextReviewDate DateTime? // Erneute Prüfung am
notes String? @db.Text
energyDetails EnergyContractDetails?
@@ -1,8 +1,11 @@
import { Request, Response } from 'express';
import { PrismaClient } from '@prisma/client';
import * as contractService from '../services/contract.service.js';
import * as contractCockpitService from '../services/contractCockpit.service.js';
import { ApiResponse, AuthRequest } from '../types/index.js';
const prisma = new PrismaClient();
export async function getContracts(req: AuthRequest, res: Response): Promise<void> {
try {
const { customerId, type, status, search, page, limit, tree } = req.query;
@@ -194,3 +197,48 @@ export async function getCockpit(req: AuthRequest, res: Response): Promise<void>
} as ApiResponse);
}
}
// ==================== SNOOZE (VERTRAG ZURÜCKSTELLEN) ====================
export async function snoozeContract(req: Request, res: Response): Promise<void> {
try {
const id = parseInt(req.params.id);
const { nextReviewDate, months } = req.body;
let reviewDate: Date | null = null;
if (nextReviewDate) {
// Explizites Datum angegeben
reviewDate = new Date(nextReviewDate);
} else if (months) {
// Monate angegeben → berechne Datum
reviewDate = new Date();
reviewDate.setMonth(reviewDate.getMonth() + months);
}
// Wenn beides leer → nextReviewDate wird auf null gesetzt (Snooze aufheben)
const updated = await prisma.contract.update({
where: { id },
data: { nextReviewDate: reviewDate },
select: {
id: true,
contractNumber: true,
nextReviewDate: true,
},
});
res.json({
success: true,
data: updated,
message: reviewDate
? `Vertrag zurückgestellt bis ${reviewDate.toLocaleDateString('de-DE')}`
: 'Zurückstellung aufgehoben',
} as ApiResponse);
} catch (error) {
console.error('Snooze error:', error);
res.status(500).json({
success: false,
error: 'Fehler beim Zurückstellen des Vertrags',
} as ApiResponse);
}
}
+3
View File
@@ -17,6 +17,9 @@ router.delete('/:id', authenticate, requirePermission('contracts:delete'), contr
// Follow-up contract
router.post('/:id/follow-up', authenticate, requirePermission('contracts:create'), contractController.createFollowUp);
// Snooze (Vertrag zurückstellen)
router.patch('/:id/snooze', authenticate, requirePermission('contracts:update'), contractController.snoozeContract);
// Get decrypted password
router.get('/:id/password', authenticate, requirePermission('contracts:read'), contractController.getContractPassword);
@@ -52,6 +52,7 @@ export interface CockpitSummary {
missingInvoices: number;
openTasks: number;
pendingContracts: number;
reviewDue: number; // Erneute Prüfung fällig (Snooze abgelaufen)
};
}
@@ -229,17 +230,46 @@ export async function getCockpitData(): Promise<CockpitResult> {
missingInvoices: 0,
openTasks: 0,
pendingContracts: 0,
reviewDue: 0,
},
};
for (const contract of contracts) {
const issues: CockpitIssue[] = [];
// SNOOZE-LOGIK: Prüfen ob Snooze aktiv ist (für Fristen-Unterdrückung)
let snoozeActive = false;
if (contract.nextReviewDate) {
const reviewDate = new Date(contract.nextReviewDate);
const now = new Date();
now.setHours(0, 0, 0, 0);
reviewDate.setHours(0, 0, 0, 0);
if (reviewDate > now) {
// Snooze aktiv → NUR Fristen-Warnungen unterdrücken, andere Prüfungen laufen weiter
snoozeActive = true;
} else {
// Snooze abgelaufen → "Erneute Prüfung fällig" Warnung
const daysSince = Math.floor((now.getTime() - reviewDate.getTime()) / (1000 * 60 * 60 * 24));
issues.push({
type: 'review_due',
label: 'Erneute Prüfung fällig',
urgency: daysSince > 30 ? 'critical' : 'warning',
daysRemaining: -daysSince,
details: daysSince === 0
? 'Heute zur Prüfung fällig'
: `Zur Prüfung seit ${daysSince} Tagen fällig`,
});
summary.byCategory.reviewDue++;
}
}
// Prüfen ob aktiver Folgevertrag existiert - dann keine Kündigungswarnungen nötig
const hasActiveFollowUp = contract.followUpContract?.status === 'ACTIVE';
// 1. KÜNDIGUNGSFRIST (nur wenn kein aktiver Folgevertrag)
if (!hasActiveFollowUp) {
// 1. KÜNDIGUNGSFRIST (nur wenn kein aktiver Folgevertrag UND Snooze nicht aktiv)
// Snooze unterdrückt NUR Fristen-bezogene Warnungen!
if (!hasActiveFollowUp && !snoozeActive) {
const cancellationDeadline = calculateCancellationDeadline(
contract.endDate,
contract.cancellationPeriod?.code
@@ -284,7 +314,9 @@ export async function getCockpitData(): Promise<CockpitResult> {
}
}
// 2. VERTRAGSENDE
// 2. VERTRAGSENDE (nur wenn Snooze nicht aktiv)
// Snooze unterdrückt NUR Fristen-bezogene Warnungen!
if (!snoozeActive) {
const daysToEnd = daysUntil(contract.endDate);
if (daysToEnd !== null && daysToEnd <= okDays) {
const urgency = getUrgencyByDays(daysToEnd, criticalDays, warningDays, okDays);
@@ -301,6 +333,7 @@ export async function getCockpitData(): Promise<CockpitResult> {
summary.byCategory.contractEnding++;
}
}
}
// 3. FEHLENDE PORTAL-ZUGANGSDATEN
// Benutzername kann entweder manuell (portalUsername) oder via Stressfrei-Wechseln E-Mail (stressfreiEmailId) gesetzt sein
+7 -5
View File
@@ -39,13 +39,15 @@ export async function getInvoice(energyContractDetailsId: number, invoiceId: num
/**
* Neue Rechnung hinzufügen
*
* Hinweis: Die Validierung ob ein Dokument vorhanden ist, erfolgt NICHT hier,
* da der typische Flow so aussieht:
* 1. Invoice erstellen (ohne Dokument) Invoice-ID zurückbekommen
* 2. Dokument hochladen mit der Invoice-ID
*
* Die Validierung ob alle Rechnungen Dokumente haben, erfolgt im Cockpit.
*/
export async function addInvoice(energyContractDetailsId: number, data: CreateInvoiceData) {
// Validierung: documentPath ist Pflicht, außer bei NOT_AVAILABLE
if (data.invoiceType !== 'NOT_AVAILABLE' && !data.documentPath) {
throw new Error('Dokument ist Pflicht (außer bei Typ "Nicht verfügbar")');
}
// Prüfen ob EnergyContractDetails existiert
const energyDetails = await prisma.energyContractDetails.findUnique({
where: { id: energyContractDetailsId },
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -5,8 +5,8 @@
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>OpenCRM</title>
<script type="module" crossorigin src="/assets/index-BZmzqt4I.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BKXieHhr.css">
<script type="module" crossorigin src="/assets/index-BUCLPhDH.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BTfzRMgT.css">
</head>
<body>
<div id="root"></div>
@@ -216,12 +216,7 @@ function InvoiceModal({
const [error, setError] = useState<string | null>(null);
const createMutation = useMutation({
mutationFn: async () => {
// Validierung: Dokument ist Pflicht, außer bei NOT_AVAILABLE
if (formData.invoiceType !== 'NOT_AVAILABLE' && !selectedFile) {
throw new Error('Bitte laden Sie ein Dokument hoch');
}
mutationFn: async (file: File) => {
// 1. Invoice erstellen
const result = await invoiceApi.addInvoice(ecdId, {
invoiceDate: formData.invoiceDate,
@@ -229,9 +224,9 @@ function InvoiceModal({
notes: formData.notes || undefined,
});
// 2. Upload file if selected
if (selectedFile && result.data?.id) {
await invoiceApi.uploadDocument(result.data.id, selectedFile);
// 2. Upload file
if (result.data?.id) {
await invoiceApi.uploadDocument(result.data.id, file);
}
return result;
@@ -245,13 +240,26 @@ function InvoiceModal({
},
});
const updateMutation = useMutation({
const createWithoutFileMutation = useMutation({
mutationFn: async () => {
// Validierung: Dokument ist Pflicht, außer bei NOT_AVAILABLE
if (formData.invoiceType !== 'NOT_AVAILABLE' && !invoice?.documentPath && !selectedFile) {
throw new Error('Bitte laden Sie ein Dokument hoch');
}
// Für NOT_AVAILABLE Typ - kein Dokument erforderlich
return await invoiceApi.addInvoice(ecdId, {
invoiceDate: formData.invoiceDate,
invoiceType: formData.invoiceType,
notes: formData.notes || undefined,
});
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['contract', contractId.toString()] });
onClose();
},
onError: (err: Error) => {
setError(err.message);
},
});
const updateMutation = useMutation({
mutationFn: async (file: File | null) => {
// 1. Invoice aktualisieren
const result = await invoiceApi.updateInvoice(ecdId, invoice!.id, {
invoiceDate: formData.invoiceDate,
@@ -259,9 +267,9 @@ function InvoiceModal({
notes: formData.notes || undefined,
});
// 2. Upload file if selected
if (selectedFile) {
await invoiceApi.uploadDocument(invoice!.id, selectedFile);
// 2. Upload file if provided
if (file) {
await invoiceApi.uploadDocument(invoice!.id, file);
}
return result;
@@ -280,9 +288,22 @@ function InvoiceModal({
setError(null);
if (isEditing) {
updateMutation.mutate();
// Edit-Modus: Dokument ist Pflicht, außer bei NOT_AVAILABLE oder wenn schon vorhanden
if (formData.invoiceType !== 'NOT_AVAILABLE' && !invoice?.documentPath && !selectedFile) {
setError('Bitte laden Sie ein Dokument hoch');
return;
}
updateMutation.mutate(selectedFile);
} else {
createMutation.mutate();
// Add-Modus: Dokument ist Pflicht, außer bei NOT_AVAILABLE
if (formData.invoiceType === 'NOT_AVAILABLE') {
createWithoutFileMutation.mutate();
} else if (!selectedFile) {
setError('Bitte laden Sie ein Dokument hoch');
return;
} else {
createMutation.mutate(selectedFile);
}
}
};
@@ -302,7 +323,7 @@ function InvoiceModal({
}
};
const isPending = createMutation.isPending || updateMutation.isPending;
const isPending = createMutation.isPending || createWithoutFileMutation.isPending || updateMutation.isPending;
return (
<Modal isOpen={isOpen} onClose={onClose} title={isEditing ? 'Rechnung bearbeiten' : 'Rechnung hinzufügen'}>
@@ -1,10 +1,12 @@
import { useState, useMemo, useEffect } from 'react';
import { useQuery } from '@tanstack/react-query';
import { useState, useMemo, useEffect, useRef } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { Link, useSearchParams } from 'react-router-dom';
import { contractApi } from '../../services/api';
import Card from '../../components/ui/Card';
import Badge from '../../components/ui/Badge';
import Select from '../../components/ui/Select';
import Button from '../../components/ui/Button';
import Input from '../../components/ui/Input';
import {
AlertCircle,
AlertTriangle,
@@ -23,6 +25,9 @@ import {
Tv,
Car,
Flame,
BellOff,
RotateCcw,
Receipt,
} from 'lucide-react';
import type { CockpitContract, CockpitUrgencyLevel, ContractType } from '../../types';
@@ -77,6 +82,8 @@ const issueTypeIcons: Record<string, typeof Calendar> = {
open_tasks: ClipboardList,
pending_status: Clock,
draft_status: FileText,
review_due: RotateCcw,
missing_invoice: Receipt,
};
const categoryLabels: Record<string, string> = {
@@ -86,9 +93,11 @@ const categoryLabels: Record<string, string> = {
missingData: 'Fehlende Daten',
openTasks: 'Offene Aufgaben',
pendingContracts: 'Wartende Verträge',
missingInvoices: 'Fehlende Rechnungen',
reviewDue: 'Erneute Prüfung fällig',
};
type FilterType = 'all' | 'critical' | 'warning' | 'ok' | 'deadlines' | 'credentials' | 'data' | 'tasks';
type FilterType = 'all' | 'critical' | 'warning' | 'ok' | 'deadlines' | 'credentials' | 'data' | 'tasks' | 'review' | 'invoices';
export default function ContractCockpit() {
const [searchParams, setSearchParams] = useSearchParams();
@@ -114,6 +123,46 @@ export default function ContractCockpit() {
staleTime: 0,
});
const queryClient = useQueryClient();
const [snoozeContractId, setSnoozeContractId] = useState<number | null>(null);
const [customDate, setCustomDate] = useState('');
const snoozeDropdownRef = useRef<HTMLDivElement>(null);
// Close snooze dropdown when clicking outside
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (snoozeDropdownRef.current && !snoozeDropdownRef.current.contains(event.target as Node)) {
setSnoozeContractId(null);
setCustomDate('');
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
const snoozeMutation = useMutation({
mutationFn: ({ contractId, data }: { contractId: number; data: { months?: number; nextReviewDate?: string } }) =>
contractApi.snooze(contractId, data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['contract-cockpit'] });
setSnoozeContractId(null);
setCustomDate('');
},
});
const handleSnooze = (contractId: number, months?: number) => {
if (months) {
snoozeMutation.mutate({ contractId, data: { months } });
} else if (customDate) {
snoozeMutation.mutate({ contractId, data: { nextReviewDate: customDate } });
}
};
const handleUnsnooze = (contractId: number) => {
// Snooze aufheben: Leeres Objekt senden → nextReviewDate wird auf null gesetzt
snoozeMutation.mutate({ contractId, data: {} });
};
const toggleExpanded = (contractId: number) => {
setExpandedContracts(prev => {
const next = new Set(prev);
@@ -155,6 +204,14 @@ export default function ContractCockpit() {
return contracts.filter(c =>
c.issues.some(i => ['open_tasks', 'pending_status', 'draft_status'].includes(i.type))
);
case 'review':
return contracts.filter(c =>
c.issues.some(i => i.type === 'review_due')
);
case 'invoices':
return contracts.filter(c =>
c.issues.some(i => i.type.includes('invoice'))
);
default:
return contracts;
}
@@ -243,16 +300,98 @@ export default function ContractCockpit() {
</div>
{/* Actions */}
<div className="flex items-center gap-1 ml-4">
{/* Snooze Button */}
<div className="relative" ref={snoozeContractId === contract.id ? snoozeDropdownRef : undefined}>
<button
onClick={(e) => {
e.stopPropagation();
setSnoozeContractId(snoozeContractId === contract.id ? null : contract.id);
setCustomDate('');
}}
className="p-2 hover:bg-white hover:bg-opacity-50 rounded"
title="Zurückstellen"
>
<BellOff className="w-4 h-4" />
</button>
{/* Snooze Dropdown */}
{snoozeContractId === contract.id && (
<div
className="absolute right-0 top-full mt-1 w-56 bg-white border rounded-lg shadow-lg z-50 p-3"
onClick={(e) => e.stopPropagation()}
>
<div className="text-sm font-medium mb-2">Zurückstellen</div>
<div className="space-y-1">
<button
onClick={() => handleSnooze(contract.id, 3)}
className="w-full text-left px-3 py-2 text-sm hover:bg-gray-100 rounded"
disabled={snoozeMutation.isPending}
>
+3 Monate
</button>
<button
onClick={() => handleSnooze(contract.id, 6)}
className="w-full text-left px-3 py-2 text-sm hover:bg-gray-100 rounded bg-blue-50 border-blue-200"
disabled={snoozeMutation.isPending}
>
+6 Monate <span className="text-xs text-gray-500">(Empfohlen)</span>
</button>
<button
onClick={() => handleSnooze(contract.id, 12)}
className="w-full text-left px-3 py-2 text-sm hover:bg-gray-100 rounded"
disabled={snoozeMutation.isPending}
>
+12 Monate
</button>
</div>
<div className="border-t mt-2 pt-2">
<label className="text-xs text-gray-500 block mb-1">Eigenes Datum:</label>
<div className="flex gap-2">
<Input
type="date"
value={customDate}
onChange={(e) => setCustomDate(e.target.value)}
className="flex-1 text-sm"
min={new Date().toISOString().split('T')[0]}
/>
<Button
size="sm"
onClick={() => handleSnooze(contract.id)}
disabled={!customDate || snoozeMutation.isPending}
>
OK
</Button>
</div>
</div>
{/* Snooze aufheben - zeige nur wenn review_due Issue existiert */}
{contract.issues.some(i => i.type === 'review_due') && (
<div className="border-t mt-2 pt-2">
<button
onClick={() => handleUnsnooze(contract.id)}
className="w-full text-left px-3 py-2 text-sm hover:bg-red-50 text-red-600 rounded flex items-center gap-2"
disabled={snoozeMutation.isPending}
>
<RotateCcw className="w-4 h-4" />
Snooze aufheben
</button>
</div>
)}
</div>
)}
</div>
<Link
to={`/contracts/${contract.id}`}
state={{ from: 'cockpit', filter: filter !== 'all' ? filter : undefined }}
className="ml-4 p-2 hover:bg-white hover:bg-opacity-50 rounded"
className="p-2 hover:bg-white hover:bg-opacity-50 rounded"
onClick={(e) => e.stopPropagation()}
title="Zum Vertrag"
>
<Eye className="w-4 h-4" />
</Link>
</div>
</div>
{/* Expanded: Issues */}
{isExpanded && (
@@ -387,6 +526,8 @@ export default function ContractCockpit() {
{ value: 'credentials', label: `Zugangsdaten (${summary.byCategory.missingCredentials})` },
{ value: 'data', label: `Fehlende Daten (${summary.byCategory.missingData})` },
{ value: 'tasks', label: `Aufgaben/Status (${summary.byCategory.openTasks + summary.byCategory.pendingContracts})` },
{ value: 'review', label: `Erneute Prüfung (${summary.byCategory.reviewDue || 0})` },
{ value: 'invoices', label: `Fehlende Rechnungen (${summary.byCategory.missingInvoices || 0})` },
]}
className="w-64"
/>
@@ -12,7 +12,7 @@ import Badge from '../../components/ui/Badge';
import Input from '../../components/ui/Input';
import Modal from '../../components/ui/Modal';
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 } 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 } from 'lucide-react';
import { calculateConsumption, calculateCosts } from '../../utils/energyCalculations';
import CopyButton, { CopyableBlock } from '../../components/ui/CopyButton';
import type { ContractType, ContractStatus, SimCard, MeterReading, ContractTask, ContractTaskSubtask } from '../../types';
@@ -1243,6 +1243,9 @@ export default function ContractDetail() {
// Status-Info Modal
const [showStatusInfo, setShowStatusInfo] = useState(false);
// Un-Snooze Bestätigungsmodal
const [showUnsnoozeConfirm, setShowUnsnoozeConfirm] = useState(false);
const { data, isLoading } = useQuery({
queryKey: ['contract', id],
queryFn: () => contractApi.getById(contractId),
@@ -1270,6 +1273,20 @@ export default function ContractDetail() {
},
});
// Un-Snooze Mutation
const unsnoozeMutation = useMutation({
mutationFn: () => contractApi.snooze(contractId, {}),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['contract', id] });
queryClient.invalidateQueries({ queryKey: ['contract-cockpit'] });
setShowUnsnoozeConfirm(false);
},
onError: (error) => {
console.error('Un-Snooze Fehler:', error);
alert(`Fehler beim Aufheben der Zurückstellung: ${error instanceof Error ? error.message : 'Unbekannter Fehler'}`);
},
});
// Mutation für Kündigungsbestätigungsdatum
const updateCancellationDateMutation = useMutation({
mutationFn: (date: string | null) => {
@@ -1446,6 +1463,22 @@ export default function ContractDetail() {
>
<Info className="w-4 h-4" />
</button>
{/* Snooze-Hinweis wenn nextReviewDate in der Zukunft */}
{c.nextReviewDate && new Date(c.nextReviewDate) > new Date() && (
<div className="flex items-center gap-1 px-2 py-1 bg-amber-100 text-amber-800 rounded-full text-xs">
<BellOff className="w-3 h-3" />
<span>Zurückgestellt bis {new Date(c.nextReviewDate).toLocaleDateString('de-DE')}</span>
{hasPermission('contracts:update') && (
<button
onClick={() => setShowUnsnoozeConfirm(true)}
className="ml-1 p-0.5 hover:bg-amber-200 rounded"
title="Zurückstellung aufheben"
>
<X className="w-3 h-3" />
</button>
)}
</div>
)}
</div>
{c.customer && (
<p className="text-gray-500 ml-10">
@@ -2647,6 +2680,34 @@ export default function ContractDetail() {
{/* Status-Info Modal */}
<StatusInfoModal isOpen={showStatusInfo} onClose={() => setShowStatusInfo(false)} />
{/* Un-Snooze Bestätigungsmodal */}
<Modal
isOpen={showUnsnoozeConfirm}
onClose={() => setShowUnsnoozeConfirm(false)}
title="Zurückstellung aufheben?"
>
<div className="space-y-4">
<p className="text-gray-700">
Möchten Sie die Zurückstellung für diesen Vertrag wirklich aufheben?
</p>
<p className="text-sm text-gray-500">
Der Vertrag wird danach wieder im Cockpit angezeigt, wenn Fristen anstehen oder abgelaufen sind.
</p>
<div className="flex justify-end gap-3 pt-4">
<Button variant="secondary" onClick={() => setShowUnsnoozeConfirm(false)}>
Abbrechen
</Button>
<Button
variant="danger"
onClick={() => unsnoozeMutation.mutate()}
disabled={unsnoozeMutation.isPending}
>
{unsnoozeMutation.isPending ? 'Wird aufgehoben...' : 'Ja, aufheben'}
</Button>
</div>
</div>
</Modal>
</div>
);
}
@@ -946,6 +946,14 @@ export default function ContractForm() {
<Input label="Vorversorger" {...register('previousProviderName')} />
<Input label="Kundennr. beim Vorversorger" {...register('previousCustomerNumber')} />
</div>
{/* Hinweis für Zählerstände und Rechnungen */}
{isEdit && (
<div className="mt-4 p-3 bg-blue-50 border border-blue-200 rounded-lg text-sm text-blue-700">
<strong>Hinweis:</strong> Zählerstände und Rechnungen werden in der{' '}
<span className="font-medium">Vertragsdetailansicht</span> verwaltet, nicht hier im Bearbeitungsformular.
</div>
)}
</Card>
)}
+5
View File
@@ -641,6 +641,11 @@ export const contractApi = {
const res = await api.get<ApiResponse<import('../types').CockpitResult>>('/contracts/cockpit');
return res.data;
},
// Snooze: Vertrag zurückstellen
snooze: async (id: number, data: { nextReviewDate?: string; months?: number }) => {
const res = await api.patch<ApiResponse<{ id: number; contractNumber: string; nextReviewDate: string | null }>>(`/contracts/${id}/snooze`, data);
return res.data;
},
};
// Contract Tasks (Aufgaben)
+4
View File
@@ -335,6 +335,8 @@ export interface Contract {
mobileDetails?: MobileContractDetails;
tvDetails?: TvContractDetails;
carInsuranceDetails?: CarInsuranceDetails;
// Snooze: Vertrag zurückstellen
nextReviewDate?: string;
followUpContract?: {
id: number;
contractNumber: string;
@@ -500,8 +502,10 @@ export interface CockpitSummary {
contractEnding: number;
missingCredentials: number;
missingData: number;
missingInvoices: number;
openTasks: number;
pendingContracts: number;
reviewDue: number;
};
}