Vertrag: Auto-CANCELLED bei Kuendigungsbestaetigung + Cockpit-Filter

1) Auto-Status: Wird eine Kuendigungsbestaetigung hinzugefuegt (Datum
   ueber Formular und/oder Dokument-Upload), wird der Vertrag von ACTIVE
   auf CANCELLED gesetzt und das Vertragsende = Kuendigungsdatum.
   Zentrale Funktion maybeCancelOnCancellationConfirmation (idempotent,
   nur aus ACTIVE). Upload-Route ersetzt die alte Inline-Logik (setzt
   jetzt auch endDate); Update-Controller triggert nur bei neu/geaendertem
   Bestaetigungsdatum (manuelle Status-Korrekturen bleiben unangetastet).

2) Cockpit-Filter 'Kuendigungsbestaetigung': neue Liste
   cancellationConfirmations (Vertraege mit Bestaetigung in Status
   ACTIVE/DRAFT/CANCELLED) + Filter-Option im Cockpit-Dropdown. Eigene
   Liste, weil bereits CANCELLED-Vertraege mangels Issue sonst nicht in
   der Cockpit-Liste auftauchen.

Beides lokal verifiziert (Helper: ACTIVE->CANCELLED + endDate; Cockpit:
Vertrag erscheint in der Liste mit korrektem Status).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-08 15:13:05 +02:00
co-authored by Claude Opus 4.8
parent 5423c83009
commit 8169c74d8e
7 changed files with 159 additions and 20 deletions
@@ -112,7 +112,7 @@ const categoryLabels: Record<string, string> = {
missingConsents: 'Fehlende Einwilligungen',
};
type FilterType = 'all' | 'critical' | 'warning' | 'ok' | 'deadlines' | 'credentials' | 'data' | 'tasks' | 'review' | 'invoices' | 'consents';
type FilterType = 'all' | 'critical' | 'warning' | 'ok' | 'deadlines' | 'credentials' | 'data' | 'tasks' | 'review' | 'invoices' | 'consents' | 'cancellation_confirmations';
export default function ContractCockpit() {
const [searchParams, setSearchParams] = useSearchParams();
@@ -238,10 +238,14 @@ export default function ContractCockpit() {
return contracts.filter(c =>
c.issues.some(i => ['missing_consents', 'consent_withdrawn'].includes(i.type))
);
case 'cancellation_confirmations':
// Eigene Liste vom Backend (Verträge mit Kündigungsbestätigung in
// Status ACTIVE/DRAFT/CANCELLED), nicht die issue-basierte Liste.
return cockpitData?.data?.cancellationConfirmations ?? [];
default:
return contracts;
}
}, [cockpitData?.data?.contracts, filter]);
}, [cockpitData?.data?.contracts, cockpitData?.data?.cancellationConfirmations, filter]);
if (isLoading) {
return (
@@ -742,8 +746,9 @@ export default function ContractCockpit() {
{ value: 'review', label: `Erneute Prüfung (${summary.byCategory.reviewDue || 0})` },
{ value: 'invoices', label: `Fehlende Rechnungen (${summary.byCategory.missingInvoices || 0})` },
{ value: 'consents', label: `Einwilligungen (${summary.byCategory.missingConsents || 0})` },
{ value: 'cancellation_confirmations', label: `Kündigungsbestätigung (${cockpitData.data.cancellationConfirmations?.length || 0})` },
]}
className="w-64"
className="w-72"
/>
<span className="text-sm text-gray-500">
{filteredContracts.length} Verträge angezeigt
+1
View File
@@ -842,6 +842,7 @@ export interface ReportedMeterReading {
export interface CockpitResult {
contracts: CockpitContract[];
cancellationConfirmations: CockpitContract[];
documentAlerts: DocumentAlert[];
reportedReadings: ReportedMeterReading[];
summary: CockpitSummary;