Schreiben als E-Mail uebernehmen

Im Schreiben-Editor gibt es jetzt den Umschalter Brief/E-Mail. Die
E-Mail-Fassung wird aus dem - womoeglich bearbeiteten - Brieftext
abgeleitet: ohne Absenderblock, Anschrift und Datumszeile, aber mit dem
Bestellungsabsatz. Betreff und Text einzeln kopierbar.

Die offene Entscheidung aus dem Issue (mailto oder .eml) ist nach
Rueckfrage mit BEIDES beantwortet:

- mailto fuer kurze Texte. Der Knopf verschwindet, sobald der kodierte
  Link 1800 Zeichen uebersteigt - mailto uebergibt den Text in der
  Adresszeile, deren Laenge nirgends zugesichert ist, und ein halbierter
  Brief faellt erst beim Empfaenger auf. Die Grenze wird NACH der
  URL-Kodierung gerechnet; 900 Umlaute sind roh harmlos und kodiert
  weit drueber.
- .eml immer: RFC 5322 mit X-Unsent: 1, quoted-printable fuer den Rumpf,
  RFC-2047-Kodierung fuer Umlaute im Betreff. Bewusst ohne From-Kopf -
  ein erfundener Absender wuerde je nach Programm uebernommen.

Beide Knoepfe erscheinen nur mit E-Mail-Adresse (aus dem Kontakt
uebernommen oder von Hand). Ohne Adresse bleibt das Kopieren.

Die Ablage kennt den Unterschied: Eine E-Mail heisst in der Akte
"E-Mail: ...", und abgelegt wird die E-Mail-Fassung samt
Empfaengeradresse im Kopf der PDF - in der Akte soll stehen, was
tatsaechlich hinausging.

Sicherung in emailFassung(): Findet sich keine Anrede (frei umgebauter
Text), wird NICHTS entfernt. Lieber ein Briefkopf zu viel in der Mail
als stillschweigend verlorener Inhalt.

Geprueft: 20 Einheitstests, vier End-to-End-Tests auf beiden
Geraeteprofilen. 590 Unit-Tests, 81 e2e, null Typfehler.

Refs #5
This commit is contained in:
Florian Wilde
2026-08-23 11:02:00 +02:00
parent a52523fc97
commit b59d11c9b4
15 changed files with 877 additions and 37 deletions
+21 -2
View File
@@ -15,6 +15,7 @@ import {
type SchreibenArt,
} from '@/lib/schreiben/arten'
import { briefPdfErzeugen } from '@/lib/schreiben/pdf'
import { emailFassung } from '@/lib/schreiben/email'
/**
* Server Actions rund um erzeugte Schreiben.
@@ -51,6 +52,9 @@ const ablegenSchema = z.object({
wiedervorlageAm: z.string().trim().default(''),
vertragId: z.string().trim().default(''),
vorgangId: z.string().trim().default(''),
/** "BRIEF" oder "EMAIL" - bestimmt Fassung und Kennzeichnung. */
versandweg: z.enum(['BRIEF', 'EMAIL']).default('BRIEF'),
empfaengerEmail: z.string().trim().max(200).default(''),
})
/**
@@ -73,6 +77,8 @@ export async function schreibenAblegen(
betreff: daten.get('betreff'),
text: daten.get('text'),
empfaengerName: daten.get('empfaengerName') ?? '',
versandweg: daten.get('versandweg') ?? 'BRIEF',
empfaengerEmail: daten.get('empfaengerEmail') ?? '',
versandAm: daten.get('versandAm') ?? '',
wiedervorlageAm: daten.get('wiedervorlageAm') ?? '',
vertragId: daten.get('vertragId') ?? '',
@@ -110,7 +116,20 @@ export async function schreibenAblegen(
if (werte.vorgangId && !vorgang) return { fehler: 'Diesen Vorgang gibt es nicht (mehr).' }
// --- Datei zuerst -------------------------------------------------------
const pdf = await briefPdfErzeugen(werte.text, versandAm)
// Bei einer E-Mail wird die E-Mail-Fassung abgelegt, nicht der
// Brieftext: In der Akte soll stehen, was tatsaechlich hinausging -
// samt Empfaengeradresse im Kopf der PDF.
const istEmail = werte.versandweg === 'EMAIL'
const fassung = istEmail ? emailFassung(werte.text, werte.betreff) : null
const ablageText = fassung
? [
`E-Mail an: ${werte.empfaengerEmail || werte.empfaengerName || '[Adresse]'}`,
`Betreff: ${fassung.betreff}`,
'',
fassung.text,
].join('\n')
: werte.text
const pdf = await briefPdfErzeugen(ablageText, versandAm)
const dateiname = dateinameFuerSchreiben(art, werte.empfaengerName || null, versandAm, 'pdf')
const ablage = await ablegenMitTyp(betreuteId, 'dokumente', dateiname, pdf, 'application/pdf')
@@ -118,7 +137,7 @@ export async function schreibenAblegen(
const dokument = await db.dokument.create({
data: {
betreuteId,
titel: werte.betreff.slice(0, 200),
titel: (istEmail ? `E-Mail: ${werte.betreff}` : werte.betreff).slice(0, 200),
art: 'SCHREIBEN',
richtung: 'AUSGANG',
schreibenVom: versandAm,