Neuer Vertragsstatus "Gekuendigt / bestaetigt" + Kuendigungs-Workflow
Bisheriges "Gekuendigt" (CANCELLED) umbenannt in "Gekuendigt / Bestaetigung
abwarten" und wird jetzt automatisch gesetzt, sobald ein Kuendigungsschreiben
hochgeladen wird. Neuer Status CANCELLED_CONFIRMED ("Gekuendigt / bestaetigt")
wird automatisch gesetzt, sobald ein Kuendigungsbestaetigungsdatum vorliegt
(Dokument fuellt das Datum oder manuell) + Vertragsende = Kuendigungsdatum.
Schema: Enum-Wert CANCELLED_CONFIRMED + Migration (idempotentes MODIFY COLUMN);
Daten-Migration hebt bestehende CANCELLED (alte Logik: nur bei Bestaetigung
gesetzt) auf CANCELLED_CONFIRMED.
Backend: neue Trigger maybeMarkAwaitingConfirmationOnLetter (Schreiben->CANCELLED)
im Upload-Handler; maybeCancelOnCancellationConfirmation setzt jetzt
CANCELLED_CONFIRMED (auch aus CANCELLED). Cockpit-Semantik mitgewandert
(Fristen-Skip/"beendet" fuer CANCELLED_CONFIRMED; Ladeliste + Kuendigungs-
bestaetigungs-Filter erweitert).
Frontend: Labels/Farben/Status-Erklaerungen + Status-Dropdown in ContractList,
ContractDetail, ContractForm, ContractDetailModal, CustomerDetail
(CANCELLED orange "abwarten", CANCELLED_CONFIRMED rot).
Verifiziert: tsc+build gruen; Schreiben->CANCELLED, Bestaetigung->
CANCELLED_CONFIRMED+Enddatum; Daten-Migration idempotent.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -207,7 +207,7 @@ export async function getCockpitData(opts?: { customerIds?: number[] }): Promise
|
||||
const contracts = await prisma.contract.findMany({
|
||||
where: {
|
||||
status: {
|
||||
in: ['ACTIVE', 'PENDING', 'DRAFT', 'CANCELLED', 'DEACTIVATED', 'EXPIRED', 'ONGOING'],
|
||||
in: ['ACTIVE', 'PENDING', 'DRAFT', 'CANCELLED', 'CANCELLED_CONFIRMED', 'DEACTIVATED', 'EXPIRED', 'ONGOING'],
|
||||
},
|
||||
...customerScopeFilter,
|
||||
},
|
||||
@@ -363,8 +363,10 @@ export async function getCockpitData(opts?: { customerIds?: number[] }): Promise
|
||||
// Fristen semantisch keinen Sinn ergeben:
|
||||
// - ONGOING : "Läuft weiter auf unbestimmte Zeit" → kein Ende
|
||||
// - DEACTIVATED : manuell beendet/archiviert → nichts mehr zu erinnern
|
||||
// - CANCELLED : Kündigung schon eingereicht → Vertragsende ist
|
||||
// die Konsequenz der Kündigung, keine Frist mehr
|
||||
// - CANCELLED_CONFIRMED : Kündigung vom Anbieter bestätigt →
|
||||
// Vertragsende steht fest, keine Frist mehr.
|
||||
// (CANCELLED = nur "Bestätigung abwarten" skippt NICHT,
|
||||
// damit die "Kündigungsbestätigung fehlt"-Warnung greift.)
|
||||
// Die Schlussrechnung-Fehlt-Warnung (Block 13a) bleibt aktiv, weil
|
||||
// sie WEGEN dieser Status feuert (kein separater Guard). DSGVO-/
|
||||
// Consent-Checks (Block 8) sind pro Kunde, nicht pro Vertrag, und
|
||||
@@ -375,7 +377,7 @@ export async function getCockpitData(opts?: { customerIds?: number[] }): Promise
|
||||
const skipDeadlines =
|
||||
contract.status === 'ONGOING'
|
||||
|| contract.status === 'DEACTIVATED'
|
||||
|| contract.status === 'CANCELLED';
|
||||
|| contract.status === 'CANCELLED_CONFIRMED';
|
||||
|
||||
// 1. KÜNDIGUNGSFRIST (nur wenn kein aktiver Folgevertrag UND Snooze nicht aktiv)
|
||||
// Snooze unterdrückt NUR Fristen-bezogene Warnungen!
|
||||
@@ -628,9 +630,11 @@ export async function getCockpitData(opts?: { customerIds?: number[] }): Promise
|
||||
const now = new Date();
|
||||
now.setHours(0, 0, 0, 0);
|
||||
|
||||
// 13a. SCHLUSSRECHNUNG FEHLT (nur wenn Vertrag gekündigt/deaktiviert ist)
|
||||
// "Beendet" = CANCELLED oder DEACTIVATED (nicht nur Laufzeit abgelaufen!)
|
||||
const isContractTerminated = contract.status === 'CANCELLED' || contract.status === 'DEACTIVATED';
|
||||
// 13a. SCHLUSSRECHNUNG FEHLT (nur wenn Vertrag bestätigt gekündigt/deaktiviert)
|
||||
// "Beendet" = CANCELLED_CONFIRMED (Kündigung bestätigt) oder DEACTIVATED
|
||||
// (nicht nur Laufzeit abgelaufen; NICHT "Bestätigung abwarten"/CANCELLED,
|
||||
// da dort das Vertragsende noch nicht feststeht).
|
||||
const isContractTerminated = contract.status === 'CANCELLED_CONFIRMED' || contract.status === 'DEACTIVATED';
|
||||
|
||||
if (isContractTerminated) {
|
||||
const hasFinalInvoice = invoices.some(inv => inv.invoiceType === 'FINAL');
|
||||
@@ -783,7 +787,7 @@ export async function getCockpitData(opts?: { customerIds?: number[] }): Promise
|
||||
// CANCELLED/EXPIRED. Eigene Liste, weil solche Verträge (v.a. bereits
|
||||
// CANCELLED/EXPIRED) sonst mangels „Issue" nicht in der Cockpit-Liste
|
||||
// auftauchen.
|
||||
const CANCEL_CONF_STATUSES: ContractStatus[] = ['ACTIVE', 'DRAFT', 'CANCELLED', 'EXPIRED'];
|
||||
const CANCEL_CONF_STATUSES: ContractStatus[] = ['ACTIVE', 'DRAFT', 'CANCELLED', 'CANCELLED_CONFIRMED', 'EXPIRED'];
|
||||
const cancellationConfirmations: CockpitContract[] = contracts
|
||||
.filter(
|
||||
(c) =>
|
||||
|
||||
Reference in New Issue
Block a user