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
+15 -1
View File
@@ -11,7 +11,7 @@ import { logChange } from '../services/audit.service.js';
import { sanitizeContract, sanitizeContractStrict, sanitizeContracts, sanitizeContractsStrict, stripHtml, sanitizeNotes, validateContractDocumentType, validateOptionalIsoDate, isContractIdentifierField, validateContractIdentifier, validatePortalUsername } from '../utils/sanitize.js';
import { ApiError } from '../utils/apiError.js';
import { canAccessContract, canAccessCustomer } from '../utils/accessControl.js';
import { maybeActivateOnDeliveryConfirmation, withContractDocumentLock } from '../services/contractStatusScheduler.service.js';
import { maybeActivateOnDeliveryConfirmation, maybeCancelOnCancellationConfirmation, withContractDocumentLock } from '../services/contractStatusScheduler.service.js';
/**
* Walk-and-clean: strippt HTML/Script-/URI-Schemata in allen String-Werten
@@ -232,6 +232,20 @@ export async function updateContract(req: AuthRequest, res: Response): Promise<v
const sanitizedBody = sanitizeContractBody(req.body);
const contract = await contractService.updateContract(contractId, sanitizedBody as any);
// Wenn über das Formular ein (neues/geändertes) Kündigungsbestätigungs-
// datum gesetzt wurde: Vertrag ACTIVE → CANCELLED + Vertragsende =
// Kündigungsdatum. Nur bei tatsächlicher Änderung des Datums auslösen,
// damit manuelle Status-Korrekturen nicht sofort zurückgesetzt werden.
{
const asDay = (d: unknown) =>
d ? new Date(d as string).toISOString().split('T')[0] : null;
const beforeConf = asDay((before as any)?.cancellationConfirmationDate);
const afterConf = asDay((sanitizedBody as any)?.cancellationConfirmationDate);
if (afterConf && afterConf !== beforeConf) {
await maybeCancelOnCancellationConfirmation(contractId, req);
}
}
// Geänderte Felder ermitteln
const changes: Record<string, { von: unknown; nach: unknown }> = {};
const fieldLabels: Record<string, string> = {