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
+50 -17
View File
@@ -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,21 +314,24 @@ export async function getCockpitData(): Promise<CockpitResult> {
}
}
// 2. VERTRAGSENDE
const daysToEnd = daysUntil(contract.endDate);
if (daysToEnd !== null && daysToEnd <= okDays) {
const urgency = getUrgencyByDays(daysToEnd, criticalDays, warningDays, okDays);
if (urgency !== 'none') {
issues.push({
type: 'contract_ending',
label: 'Vertragsende',
urgency,
daysRemaining: daysToEnd,
details: daysToEnd < 0
? `Vertrag seit ${Math.abs(daysToEnd)} Tagen abgelaufen!`
: `Noch ${daysToEnd} Tage bis Vertragsende`,
});
summary.byCategory.contractEnding++;
// 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);
if (urgency !== 'none') {
issues.push({
type: 'contract_ending',
label: 'Vertragsende',
urgency,
daysRemaining: daysToEnd,
details: daysToEnd < 0
? `Vertrag seit ${Math.abs(daysToEnd)} Tagen abgelaufen!`
: `Noch ${daysToEnd} Tage bis Vertragsende`,
});
summary.byCategory.contractEnding++;
}
}
}
+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 },