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
@@ -0,0 +1,8 @@
-- Vertrag: „Portaldaten nicht möglich / nicht benötigt"-Marker.
-- Wenn gesetzt, unterdrückt das Cockpit die Warnung „Portal-Zugangsdaten
-- fehlen" für diesen Vertrag (Anbieter ohne Portal / Kunde bekommt keine
-- Zugangsdaten). Bestehende Verträge bleiben auf FALSE, damit die
-- Warnung wie bisher greift.
ALTER TABLE `Contract`
ADD COLUMN IF NOT EXISTS `portalCredentialsNotRequired` BOOLEAN NOT NULL DEFAULT FALSE;
+5
View File
@@ -765,6 +765,11 @@ model Contract {
stressfreiEmailId Int?
stressfreiEmail StressfreiEmail? @relation(fields: [stressfreiEmailId], references: [id])
// Portaldaten bewusst nicht hinterlegt (Anbieter hat kein Portal oder
// Kunde bekommt keine Zugangsdaten). Wenn true, unterdrückt das Cockpit
// die „Portal-Zugangsdaten fehlen"-Warnung für diesen Vertrag.
portalCredentialsNotRequired Boolean @default(false)
// Snooze: Vertrag zurückstellen bis Datum (für Cockpit)
nextReviewDate DateTime? // Erneute Prüfung am
+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