feat: pro Mandant editierbare E-Mail-Vorlagen

Zweistufig wie der Werkzeugkatalog: Betreiber pflegt den plattformweiten
Standard (/betreiber/email-vorlagen), jeder Mandant kann ihn für sich
übersteuern (/verwaltung/email-vorlagen) - ResolveEmailVorlage nutzt die
eigene Vorlage falls vorhanden, sonst fällt sie auf den Plattform-
Standard zurück. Passwort-Zurücksetzen ist die einzige aktuell
existierende E-Mail und nutzt jetzt diese Vorlage statt Hardcoding.

Bug beim Live-Verifizieren gefunden: UNIQUE(account_id, typ) verhindert
bei NULLABLE account_id keine Duplikate (NULL != NULL in SQL) - jedes
Speichern des Plattform-Standards erzeugte eine neue Zeile statt sie zu
aktualisieren. Fix: zwei partielle Unique-Indizes statt eines
gemeinsamen Constraints, mit Regressionstest abgesichert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
noroot
2026-09-01 10:06:09 +02:00
parent b0d6b00045
commit d3b171f721
13 changed files with 749 additions and 3 deletions

View File

@@ -24,6 +24,7 @@
<span class="stat-card-label">Audit-Log (neueste)</span>
</a>
</li>
<li><a class="stat-card stat-card-action" href="/betreiber/email-vorlagen"><span class="stat-card-label">E-Mail-Vorlagen &rarr;</span></a></li>
</ul>
</div>
</body>

View File

@@ -0,0 +1,54 @@
{{define "email-vorlagen-liste"}}<!doctype html>
<html lang="de">
<head>{{template "head" .}}</head>
<body>
{{template "nav" .Nav}}
<div class="page page-wide">
<div class="page-header">
<div>
<h1>{{.Title}}</h1>
<p class="hinweis">
Betreff und Text der vom System versendeten E-Mails. Platzhalter
in geschweiften Klammern (z. B. <code>{{"{{link}}"}}</code>)
werden beim Versand automatisch ersetzt.
</p>
</div>
</div>
{{if .Error}}<p class="fehler">{{.Error}}</p>{{end}}
{{if .Gespeichert}}<p class="hinweis">Gespeichert.</p>{{end}}
{{range .Zeilen}}
<div class="form-card">
<form method="post" action="{{$.ActionBase}}">
<input type="hidden" name="typ" value="{{.Typ}}">
<fieldset class="form-section">
<legend>{{.Label}}{{if .IstUebersteuert}} <span class="hinweis">(eigene Vorlage)</span>{{else if eq $.ActionBase "/verwaltung/email-vorlagen"}} <span class="hinweis">(Plattform-Standard, nicht angepasst)</span>{{end}}</legend>
<p class="hinweis">Verfügbare Platzhalter: {{.Platzhalter}}</p>
<div class="form-grid">
<div class="form-full">
<label for="betreff-{{.Typ}}">Betreff</label>
<input type="text" id="betreff-{{.Typ}}" name="betreff" value="{{.Betreff}}" required>
</div>
<div class="form-full">
<label for="text-{{.Typ}}">Text</label>
<textarea id="text-{{.Typ}}" name="text" rows="8" required>{{.Text}}</textarea>
</div>
</div>
</fieldset>
<div class="form-actions">
<button type="submit">Speichern</button>
</div>
</form>
{{if and .IstUebersteuert (eq $.ActionBase "/verwaltung/email-vorlagen")}}
<div class="form-gefahrenzone">
<form method="post" action="/verwaltung/email-vorlagen/zuruecksetzen" onsubmit="return confirm('Eigene Vorlage löschen und zum Plattform-Standard zurückkehren?')">
<input type="hidden" name="typ" value="{{.Typ}}">
<button type="submit" class="entfernen">Auf Plattform-Standard zurücksetzen</button>
</form>
</div>
{{end}}
</div>
{{end}}
</div>
</body>
</html>
{{end}}

View File

@@ -35,6 +35,7 @@
{{if .IsAdmin}}<a href="/verwaltung/genehmiger-rollen">Genehmiger-Rollen</a>{{end}}
{{if .IsAdmin}}<a href="/verwaltung/freigabe-regeln">Freigabe-Regeln</a>{{end}}
{{if .IsAdmin}}<a href="/verwaltung/loeschfristen">Löschfristen</a>{{end}}
{{if .IsAdmin}}<a href="/verwaltung/email-vorlagen">E-Mail-Vorlagen</a>{{end}}
{{if .IsBetreiber}}<a href="/betreiber">Plattform</a>{{end}}
<form method="post" action="/logout">
<button type="submit">Abmelden</button>