fix(stressfrei): Refresh-Button nur bei provisioned + Auto-Heilung im Status-Sync

User-Feedback: der Refresh-Button war auch bei nicht-provisionierten
Adressen sichtbar (die nur als DB-Eintrag ohne Plesk-Pendant existieren).
Klick darauf gab korrekt einen Fehler, war aber unschön.

Bedingung wieder auf `emailItem.isProvisioned` einschränken. Für
historische Einträge, bei denen das Flag wegen des alten Bugs nie
gesetzt wurde, gibt es jetzt einen automatischen Reconcile-Pfad:

`syncMailboxStatus` (wird beim Öffnen jedes Edit-Modals aufgerufen)
prüft nicht mehr nur `hasMailbox`, sondern auch `isProvisioned`:
- Provider antwortet "existiert" + DB sagt isProvisioned=false
  → DB-Flag auf true ziehen + provisionedAt setzen
- Provider antwortet "nicht da" + DB sagt isProvisioned=true
  → DB-Flag auf false (Adresse wurde im Plesk-UI manuell gelöscht)
- hasMailbox wird zusätzlich konsistent gehalten

Damit heilen sich falsch markierte Adressen automatisch, sobald der
User sie einmal aufmacht zum Bearbeiten – der Refresh-Button erscheint
dann beim Re-Open.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-16 14:11:47 +02:00
parent c2ebc7cf1e
commit 51eb12b414
2 changed files with 63 additions and 38 deletions
@@ -210,7 +210,7 @@ export async function syncMailboxStatus(id: number): Promise<{
}> {
const stressfreiEmail = await prisma.stressfreiEmail.findUnique({
where: { id },
select: { email: true, hasMailbox: true },
select: { email: true, hasMailbox: true, isProvisioned: true, provisionedAt: true },
});
if (!stressfreiEmail) {
@@ -222,19 +222,42 @@ export async function syncMailboxStatus(id: number): Promise<{
// Provider-Status prüfen
const providerStatus = await checkEmailExists(localPart);
// Self-Healing für `isProvisioned`: das Flag wurde in einer früheren Code-
// Version beim Provisioning nie gesetzt → DB ist stellenweise inkonsistent
// zum Provider. Wir reconciliieren bei jedem Status-Sync mit.
const updates: Record<string, unknown> = {};
if (!providerStatus.exists) {
// Beim Provider nicht (mehr) vorhanden → DB-Flag entsprechend
if (stressfreiEmail.isProvisioned) {
updates.isProvisioned = false;
}
if (stressfreiEmail.hasMailbox) {
updates.hasMailbox = false;
}
if (Object.keys(updates).length > 0) {
await prisma.stressfreiEmail.update({ where: { id }, data: updates });
return { success: true, hasMailbox: false, wasUpdated: true };
}
return { success: true, hasMailbox: false, wasUpdated: false };
}
const providerHasMailbox = providerStatus.hasMailbox === true;
// Beim Provider vorhanden → isProvisioned auf true ziehen falls noch nicht
if (!stressfreiEmail.isProvisioned) {
updates.isProvisioned = true;
if (!stressfreiEmail.provisionedAt) {
updates.provisionedAt = new Date();
}
}
// DB aktualisieren wenn Status abweicht
const providerHasMailbox = providerStatus.hasMailbox === true;
if (stressfreiEmail.hasMailbox !== providerHasMailbox) {
await prisma.stressfreiEmail.update({
where: { id },
data: { hasMailbox: providerHasMailbox },
});
console.log(`Mailbox-Status für ${stressfreiEmail.email} aktualisiert: ${stressfreiEmail.hasMailbox} -> ${providerHasMailbox}`);
updates.hasMailbox = providerHasMailbox;
}
if (Object.keys(updates).length > 0) {
await prisma.stressfreiEmail.update({ where: { id }, data: updates });
console.log(`Stressfrei-Status für ${stressfreiEmail.email} reconciled:`, updates);
return { success: true, hasMailbox: providerHasMailbox, wasUpdated: true };
}
+32 -30
View File
@@ -3060,38 +3060,40 @@ function StressfreiEmailsTab({
>
<Edit className="w-4 h-4" />
</Button>
<Button
variant="ghost"
size="sm"
disabled={syncForwardingMutation.isPending}
onClick={() => {
const lines = [
`Weiterleitungen für ${emailItem.email} jetzt neu setzen?`,
'',
'Alle bestehenden Weiterleitungen am Provider werden ersetzt durch:',
'• die aktuelle Stamm-E-Mail des Kunden',
'• unsere Service-Weiterleitungsadresse aus den Provider-Einstellungen',
];
if (emailItem.hasMailbox) {
lines.push(
{emailItem.isProvisioned && (
<Button
variant="ghost"
size="sm"
disabled={syncForwardingMutation.isPending}
onClick={() => {
const lines = [
`Weiterleitungen für ${emailItem.email} jetzt neu setzen?`,
'',
'Zusätzlich wird das im CRM hinterlegte Mailbox-Passwort am Provider neu gesetzt.',
);
'Alle bestehenden Weiterleitungen am Provider werden ersetzt durch:',
'• die aktuelle Stamm-E-Mail des Kunden',
'• unsere Service-Weiterleitungsadresse aus den Provider-Einstellungen',
];
if (emailItem.hasMailbox) {
lines.push(
'',
'Zusätzlich wird das im CRM hinterlegte Mailbox-Passwort am Provider neu gesetzt.',
);
}
if (confirm(lines.join('\n'))) {
syncForwardingMutation.mutate(emailItem.id);
}
}}
title={
emailItem.hasMailbox
? 'Weiterleitungen + Mailbox-Passwort synchronisieren. Nützlich nach Änderung der Kunden-Stamm-E-Mail oder nach manuellem Eingriff am Provider.'
: 'Weiterleitungen synchronisieren ersetzt die Forwards am Provider durch (Kunden-Stamm-E-Mail + Service-Adresse). Nützlich nach Änderung der Stamm-E-Mail.'
}
if (confirm(lines.join('\n'))) {
syncForwardingMutation.mutate(emailItem.id);
}
}}
title={
emailItem.hasMailbox
? 'Weiterleitungen + Mailbox-Passwort synchronisieren. Nützlich nach Änderung der Kunden-Stamm-E-Mail oder nach manuellem Eingriff am Provider.'
: 'Weiterleitungen synchronisieren ersetzt die Forwards am Provider durch (Kunden-Stamm-E-Mail + Service-Adresse). Nützlich nach Änderung der Stamm-E-Mail.'
}
>
<RefreshCw
className={`w-4 h-4 ${syncForwardingMutation.isPending ? 'animate-spin' : ''}`}
/>
</Button>
>
<RefreshCw
className={`w-4 h-4 ${syncForwardingMutation.isPending ? 'animate-spin' : ''}`}
/>
</Button>
)}
{emailItem.isActive ? (
<Button
variant="ghost"