Vertrag: „Portaldaten nicht möglich / nicht benötigt"-Opt-out

Dritter Radio-Button in der Zugangsdaten-Card des Vertragsformulars.
Wenn gesetzt, unterdrückt das Cockpit die Warnung „Portal-Zugangs-
daten fehlen" für diesen Vertrag – für Anbieter ohne Portal oder
Kunden, die bewusst keine Zugangsdaten pflegen. Verstopft das
Cockpit sonst dauerhaft.

Neues Feld Contract.portalCredentialsNotRequired (Boolean, default
false) + idempotente Migration (ADD COLUMN IF NOT EXISTS). Bestand
bleibt auf false, Warnung greift wie bisher.

Beim Umschalten auf Opt-out werden portalUsername,
portalPasswordEncrypted und stressfreiEmailId server-seitig
explizit auf NULL gesetzt – Datenhygiene, damit keine verwaisten
Anmeldedaten in der DB stehen bleiben.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-07-17 14:45:32 +02:00
co-authored by Claude Opus 4.7
parent aeecc3327f
commit dcdb3cb057
7 changed files with 132 additions and 35 deletions
+16 -1
View File
@@ -233,6 +233,7 @@ interface ContractCreateData {
portalUsername?: string;
portalPassword?: string;
stressfreiEmailId?: number;
portalCredentialsNotRequired?: boolean;
notes?: string;
// Kündigungsdaten
cancellationConfirmationDate?: Date;
@@ -461,12 +462,26 @@ export async function updateContract(
? encrypt(portalPassword)
: undefined;
// Opt-out „Portaldaten nicht benötigt": Alte Anmeldedaten explizit
// wegräumen, damit nach dem Umschalten kein verwaister verschlüsselter
// Blob + Username in der DB stehen bleibt. Nur greifen, wenn das Flag
// in diesem Update wirklich auf true gesetzt wird (nicht bei jedem PUT).
const clearCredentials = contractData.portalCredentialsNotRequired === true;
// Update main contract
await prisma.contract.update({
where: { id },
data: {
...contractData,
...(portalPasswordEncrypted ? { portalPasswordEncrypted } : {}),
...(clearCredentials
? {
portalUsername: null,
portalPasswordEncrypted: null,
stressfreiEmailId: null,
}
: portalPasswordEncrypted
? { portalPasswordEncrypted }
: {}),
},
});
+17 -11
View File
@@ -443,18 +443,24 @@ export async function getCockpitData(opts?: { customerIds?: number[] }): Promise
}
// 3. FEHLENDE PORTAL-ZUGANGSDATEN
// Benutzername kann entweder manuell (portalUsername) oder via Stressfrei-Wechseln E-Mail (stressfreiEmailId) gesetzt sein
const hasUsername = contract.portalUsername || contract.stressfreiEmailId;
const hasPassword = contract.portalPasswordEncrypted;
// Benutzername kann entweder manuell (portalUsername) oder via Stressfrei-Wechseln E-Mail (stressfreiEmailId) gesetzt sein.
// Vertragsseitiges Opt-out: wenn der Anbieter kein Portal hat oder der
// Kunde bewusst keine Zugangsdaten pflegt, unterdrückt
// `portalCredentialsNotRequired` die Warnung. So verstopft die Zeile
// nicht dauerhaft das Cockpit.
if (!contract.portalCredentialsNotRequired) {
const hasUsername = contract.portalUsername || contract.stressfreiEmailId;
const hasPassword = contract.portalPasswordEncrypted;
if (!hasUsername || !hasPassword) {
issues.push({
type: 'missing_portal_credentials',
label: 'Portal-Zugangsdaten fehlen',
urgency: 'warning',
details: 'Benutzername oder Passwort für das Anbieter-Portal fehlt',
});
summary.byCategory.missingCredentials++;
if (!hasUsername || !hasPassword) {
issues.push({
type: 'missing_portal_credentials',
label: 'Portal-Zugangsdaten fehlen',
urgency: 'warning',
details: 'Benutzername oder Passwort für das Anbieter-Portal fehlt',
});
summary.byCategory.missingCredentials++;
}
}
// 4. KEINE KUNDENNUMMER BEIM ANBIETER