Eigene Vorlagen fuer Anschreiben

Wer zum dritten Mal denselben Brief tippt, tippt ihn beim vierten Mal
falsch. Neu unter Einstellungen -> Schreibvorlagen: eigene Texte
hinterlegen, beim freien Schreiben auswaehlen, Platzhalter fuellen sich
aus den Daten, die ohnehin im System stehen.

Aufbau:
- Die Ersetzung als reine Funktionen in schreiben/platzhalter.ts. Drei
  Faelle, keiner davon still: Wert einsetzen, sichtbare Luecke
  [Aktenzeichen des Gerichts], unbekannter Platzhalter markiert. Ein
  Tippfehler in der Vorlage darf das Schreiben nicht verhindern - aber
  er muss ins Auge springen.
- Die Bestellungsabsatz-Warnung ist zweistufig: Der erste Speicherversuch
  ohne ihn kommt mit einer Rueckfrage zurueck, erst die Bestaetigung
  speichert. Ohne den Nachweis der Vertretungsmacht kommt keine Auskunft,
  und das faellt erst nach vier Wochen auf. Ein ausformulierter Absatz
  zaehlt so viel wie der Platzhalter, ein halber Nachweis ("Beschluss der
  Eigentuemerversammlung") nicht.
- Vier mitgelieferte Vorlagen bleiben Code und damit unveraenderlich;
  "Als Kopie uebernehmen" legt eine eigene Fassung an. Fuer Rueckkaufswert
  und Kuendigung fuehrt die Oberflaeche weiter zuerst zu den
  fallbezogenen Erzeugern - die rechnen Kuendigungstermine aus, was eine
  Vorlage nicht kann.
- Vorlagen haengen an der betreuten Person, nicht am Benutzer: Sie
  beziehen sich auf ihren Fall, liegen im Papierkorb wie alles andere und
  sind im Vollexport ihrer Akte enthalten.

Der Editor haelt Betreff und Text als eigenen State. Beim Wechsel der
Vorlage ueberlebte der alte Entwurf die Navigation, die Vorlage kam nie
im Textfeld an - ein key auf dem Editor setzt ihn neu auf. Gefunden hat
das erst der End-to-End-Test.

Geprueft: dreizehn Einheitstests fuer die Ersetzung, sechs
End-to-End-Tests fuer Warnung, Kopie, Aufloesung und Papierkorb.

Refs #7
This commit is contained in:
Florian Wilde
2026-08-23 13:41:51 +02:00
parent 0a8878d8d2
commit f8d7752c27
21 changed files with 1443 additions and 13 deletions
+103
View File
@@ -0,0 +1,103 @@
import { describe, expect, it } from 'vitest'
import {
PLATZHALTER,
benutztePlatzhalter,
bestellungsabsatzFehlt,
platzhalterErsetzen,
} from '@/lib/schreiben/platzhalter'
/**
* Platzhalter in Schreibvorlagen.
*
* Der Grundsatz, den jede Prüfung hier absichert: NICHTS geht
* stillschweigend schief. Fehlende Daten werden zur sichtbaren Lücke,
* unbekannte Platzhalter zur Markierung — nie zu einer leeren Stelle, die
* erst der Empfänger bemerkt.
*/
describe('Ersetzen', () => {
it('setzt bekannte Werte ein', () => {
const ergebnis = platzhalterErsetzen('Sehr geehrte Damen und Herren, {{betreuer_name}} hier.', {
betreuer_name: 'Florian Wilde',
})
expect(ergebnis.text).toBe('Sehr geehrte Damen und Herren, Florian Wilde hier.')
expect(ergebnis.luecken).toHaveLength(0)
expect(ergebnis.unbekannte).toHaveLength(0)
})
it('macht aus fehlenden Daten eine sichtbare Lücke', () => {
const ergebnis = platzhalterErsetzen('Az. {{aktenzeichen_gericht}}', {})
expect(ergebnis.text).toBe('Az. [Aktenzeichen des Gerichts]')
expect(ergebnis.luecken).toEqual(['aktenzeichen_gericht'])
})
it('behandelt leere Zeichenketten wie fehlende Daten', () => {
const ergebnis = platzhalterErsetzen('{{betreuungsgericht}}', { betreuungsgericht: ' ' })
expect(ergebnis.text).toContain('[Betreuungsgericht]')
})
it('markiert unbekannte Platzhalter, statt abzubrechen', () => {
// Ein Tippfehler in der Vorlage darf das Schreiben nicht verhindern —
// aber er muss ins Auge springen.
const ergebnis = platzhalterErsetzen('Hallo {{betreur_name}}!', {})
expect(ergebnis.text).toBe('Hallo [Unbekannter Platzhalter: {{betreur_name}}]!')
expect(ergebnis.unbekannte).toEqual(['betreur_name'])
})
it('verträgt Leerzeichen in den Klammern', () => {
const ergebnis = platzhalterErsetzen('{{ betreuer_name }}', { betreuer_name: 'F. Wilde' })
expect(ergebnis.text).toBe('F. Wilde')
})
it('ersetzt mehrere Vorkommen und meldet jede Lücke nur einmal', () => {
const ergebnis = platzhalterErsetzen('{{stichtag}} und nochmal {{stichtag}}', {})
expect(ergebnis.luecken).toEqual(['stichtag'])
expect(ergebnis.text.match(/\[Stichtag\]/g)).toHaveLength(2)
})
it('lässt Text ohne Platzhalter unangetastet', () => {
const text = 'Ein Brief ohne alles. Auch { einzelne } Klammern bleiben.'
expect(platzhalterErsetzen(text, {}).text).toBe(text)
})
})
describe('Platzhalterliste', () => {
it('beschreibt jeden Platzhalter mit Beschriftung und Quelle', () => {
// Die Liste steht neben dem Editor - niemand soll Namen raten müssen.
for (const platzhalter of PLATZHALTER) {
expect(platzhalter.beschriftung.length, platzhalter.name).toBeGreaterThan(3)
expect(platzhalter.quelle.length, platzhalter.name).toBeGreaterThan(3)
}
})
it('findet die benutzten Platzhalter einer Vorlage', () => {
expect(
benutztePlatzhalter('{{betreuer_name}} an {{vertrag_gesellschaft}}, nochmal {{betreuer_name}}'),
).toEqual(['betreuer_name', 'vertrag_gesellschaft'])
})
})
describe('Bestellungsabsatz-Warnung', () => {
it('ist zufrieden mit dem Platzhalter', () => {
expect(bestellungsabsatzFehlt('Text mit {{bestellungsabsatz}} darin.')).toBe(false)
})
it('ist zufrieden mit einem von Hand geschriebenen Nachweis', () => {
// Wer den Absatz ausformuliert hat, wird nicht gegängelt.
expect(
bestellungsabsatzFehlt(
'Durch Beschluss des Amtsgerichts bin ich zum Betreuer bestellt.',
),
).toBe(false)
})
it('warnt bei einer Vorlage ohne jeden Nachweis', () => {
expect(bestellungsabsatzFehlt('Sehr geehrte Damen und Herren, bitte um Auskunft.')).toBe(true)
})
it('lässt sich nicht von einem halben Nachweis täuschen', () => {
// "Beschluss" allein (etwa der Beschluss der Eigentümerversammlung)
// ist kein Bestellungsnachweis.
expect(bestellungsabsatzFehlt('Zum Beschluss der Eigentümerversammlung...')).toBe(true)
})
})