Jeder erzeugte Brief ging bisher ohne Ruecksendeadresse hinaus: Im Absenderblock stand nur der Name, in der Datumszeile fehlte der Ort. Bei einer Versicherung oder Behoerde wirkt das unserioes und macht es der Gegenstelle unnoetig schwer zu antworten. Neu unter "Mein Konto" ein Feld fuer Strasse, PLZ und Ort. Ab da stehen sie im Absenderblock jedes Schreibens, und die Datumszeile lautet "Bayreuth, den 23.08.2026". Die Anschrift haengt am Benutzer, nicht an der betreuten Person: Sie gehoert zu mir und gilt fuer alle Akten. Nicht die Logik fehlte, sondern die Datenquelle - absenderBlock() und datumsZeile() konnten die Felder schon, briefkopfBauen() hat sie mit einem Kommentar auf null gesetzt, weil es sie im Datenmodell nicht gab. Aufbau: - Drei Felder am Benutzer, Migration ueber den Wrapper aus #24. Das war sein erster echter Einsatz: erzeugte Datei ohne Handarbeit brauchbar, DROP-INDEX-Zeilen bereits heraus. - Ein Absender-Typ statt eines blossen Namens, durchgereicht bis in den Serienlauf - der hat bisher {name} gebaut und haette die Anschrift sonst still verloren. - Zwei Platzhalter fuer eigene Vorlagen: betreuer_anschrift, betreuer_ort. Was fehlt, bleibt weg: Jede der drei Angaben ist einzeln freiwillig. Nur ein Ort ergibt eine Datumszeile mit Ort und einen Absenderblock, der weiterhin nur aus dem Namen besteht. Eine halb ausgefuellte Adresse sieht nach Fehler aus, ein blosser Name nach Absicht. Geprueft: sieben Einheitstests fuer den Absenderblock inklusive jeder Teilbefuellung und der Gegenprobe, dass nie die Heimadresse der betreuten Person im Absender landet; vier End-to-End-Tests vom leeren Zustand ueber das Speichern bis zum fertigen Brief. Refs #21
152 lines
5.5 KiB
TypeScript
152 lines
5.5 KiB
TypeScript
/**
|
|
* Platzhalter in Schreibvorlagen.
|
|
*
|
|
* Rein und ohne Datenbank. Der Vertrag ist einfach: {{name}} wird ersetzt,
|
|
* und zwar nach drei Regeln, die alle demselben Grundsatz folgen -
|
|
* NICHTS geht stillschweigend schief:
|
|
*
|
|
* 1. Bekannter Platzhalter, Daten vorhanden -> Wert eingesetzt.
|
|
* 2. Bekannter Platzhalter, Daten fehlen -> sichtbare Luecke
|
|
* "[Aktenzeichen]". Eine stille Leerstelle wuerde erst der Empfaenger
|
|
* bemerken - oder niemand.
|
|
* 3. Unbekannter Platzhalter -> markiert als
|
|
* "[Unbekannter Platzhalter: {{xyz}}]". Kein Abbruch: Ein Tippfehler
|
|
* in einer Vorlage darf das Schreiben nicht verhindern, aber er muss
|
|
* ins Auge springen. Falsch ausgefuellt ist schlimmer als
|
|
* offensichtlich leer.
|
|
*/
|
|
|
|
export type PlatzhalterName =
|
|
| 'betreute_name'
|
|
| 'betreute_geburtsdatum'
|
|
| 'betreute_anschrift'
|
|
| 'betreuer_name'
|
|
| 'betreuer_anschrift'
|
|
| 'betreuer_ort'
|
|
| 'betreuungsgericht'
|
|
| 'aktenzeichen_gericht'
|
|
| 'bestellung_datum'
|
|
| 'bestellungsabsatz'
|
|
| 'vertrag_gesellschaft'
|
|
| 'vertrag_nummer'
|
|
| 'vertrag_bezeichnung'
|
|
| 'stichtag'
|
|
| 'datum_heute'
|
|
|
|
export type PlatzhalterBeschreibung = {
|
|
name: PlatzhalterName
|
|
beschriftung: string
|
|
/** Woher der Wert kommt - steht in der Hilfe neben dem Editor. */
|
|
quelle: string
|
|
}
|
|
|
|
/**
|
|
* Die verfuegbaren Platzhalter, in der Reihenfolge der Anzeige.
|
|
*
|
|
* Diese Liste steht beim Bearbeiten NEBEN dem Editor - niemand soll
|
|
* Platzhalternamen raten muessen.
|
|
*/
|
|
export const PLATZHALTER: readonly PlatzhalterBeschreibung[] = [
|
|
{ name: 'betreute_name', beschriftung: 'Name der betreuten Person', quelle: 'Stammdaten' },
|
|
{ name: 'betreute_geburtsdatum', beschriftung: 'Geburtsdatum', quelle: 'Stammdaten' },
|
|
{ name: 'betreute_anschrift', beschriftung: 'Anschrift (Heim)', quelle: 'Stammdaten' },
|
|
{ name: 'betreuer_name', beschriftung: 'Dein Name', quelle: 'Dein Zugang' },
|
|
{
|
|
name: 'betreuer_anschrift',
|
|
beschriftung: 'Deine Anschrift',
|
|
quelle: 'Mein Konto',
|
|
},
|
|
{ name: 'betreuer_ort', beschriftung: 'Dein Ort', quelle: 'Mein Konto' },
|
|
{ name: 'betreuungsgericht', beschriftung: 'Betreuungsgericht', quelle: 'Stammdaten' },
|
|
{ name: 'aktenzeichen_gericht', beschriftung: 'Aktenzeichen des Gerichts', quelle: 'Stammdaten' },
|
|
{ name: 'bestellung_datum', beschriftung: 'Datum der Bestellung', quelle: 'Stammdaten' },
|
|
{
|
|
name: 'bestellungsabsatz',
|
|
beschriftung: 'Der ganze Bestellungsabsatz',
|
|
quelle: 'Aus Gericht, Aktenzeichen und Bestellungsdatum gebaut',
|
|
},
|
|
{ name: 'vertrag_gesellschaft', beschriftung: 'Gesellschaft', quelle: 'Vertrag, falls gewählt' },
|
|
{ name: 'vertrag_nummer', beschriftung: 'Vertragsnummer', quelle: 'Vertrag, falls gewählt' },
|
|
{ name: 'vertrag_bezeichnung', beschriftung: 'Vertragsbezeichnung', quelle: 'Vertrag, falls gewählt' },
|
|
{ name: 'stichtag', beschriftung: 'Stichtag', quelle: 'Beim Erzeugen gewählt' },
|
|
{ name: 'datum_heute', beschriftung: 'Heutiges Datum', quelle: 'Beim Erzeugen' },
|
|
]
|
|
|
|
const BEKANNT = new Set<string>(PLATZHALTER.map((p) => p.name))
|
|
|
|
/** Die sichtbare Luecke, wenn Daten fehlen: "[Aktenzeichen des Gerichts]". */
|
|
function luecke(name: PlatzhalterName): string {
|
|
const eintrag = PLATZHALTER.find((p) => p.name === name)
|
|
return `[${eintrag?.beschriftung ?? name}]`
|
|
}
|
|
|
|
export type PlatzhalterWerte = Partial<Record<PlatzhalterName, string | null>>
|
|
|
|
export type ErsetzungsErgebnis = {
|
|
text: string
|
|
/** Bekannte Platzhalter ohne Daten - als Luecke eingesetzt. */
|
|
luecken: PlatzhalterName[]
|
|
/** Unbekannte Platzhalter - markiert im Text. */
|
|
unbekannte: string[]
|
|
}
|
|
|
|
/** Findet alle {{...}}-Vorkommen. Grosszuegig beim Namen: auch Tippfehler
|
|
* sollen gefunden (und dann markiert) werden. */
|
|
const MUSTER = /\{\{\s*([a-zA-Z0-9_äöüÄÖÜß-]+)\s*\}\}/g
|
|
|
|
export function platzhalterErsetzen(
|
|
vorlage: string,
|
|
werte: PlatzhalterWerte,
|
|
): ErsetzungsErgebnis {
|
|
const luecken = new Set<PlatzhalterName>()
|
|
const unbekannte = new Set<string>()
|
|
|
|
const text = vorlage.replace(MUSTER, (treffer, roh: string) => {
|
|
const name = roh.trim()
|
|
|
|
if (!BEKANNT.has(name)) {
|
|
unbekannte.add(name)
|
|
return `[Unbekannter Platzhalter: {{${name}}}]`
|
|
}
|
|
|
|
const wert = werte[name as PlatzhalterName]
|
|
if (wert === null || wert === undefined || wert.trim() === '') {
|
|
luecken.add(name as PlatzhalterName)
|
|
return luecke(name as PlatzhalterName)
|
|
}
|
|
|
|
return wert
|
|
})
|
|
|
|
return { text, luecken: [...luecken], unbekannte: [...unbekannte] }
|
|
}
|
|
|
|
/** Welche Platzhalter benutzt eine Vorlage? Fuer die Anzeige in der Liste. */
|
|
export function benutztePlatzhalter(vorlage: string): string[] {
|
|
const gefunden = new Set<string>()
|
|
for (const treffer of vorlage.matchAll(MUSTER)) {
|
|
gefunden.add(treffer[1]!.trim())
|
|
}
|
|
return [...gefunden]
|
|
}
|
|
|
|
/**
|
|
* Fehlt der Bestellungsabsatz?
|
|
*
|
|
* Erfuellt ist die Pruefung durch den Platzhalter {{bestellungsabsatz}}
|
|
* ODER durch einen von Hand geschriebenen Absatz, der erkennbar die
|
|
* Bestellung nachweist (Beschluss + Betreuer im selben Text). Wer den
|
|
* Absatz ausformuliert hat, soll nicht gegaengelt werden.
|
|
*
|
|
* Die Warnung ist eine Warnung, kein Verbot: Ein Schreiben an die eigene
|
|
* Hausverwaltung braucht keinen Bestellungsnachweis. Aber wer ihn
|
|
* weglaesst, soll es wissentlich tun.
|
|
*/
|
|
export function bestellungsabsatzFehlt(vorlage: string): boolean {
|
|
if (/\{\{\s*bestellungsabsatz\s*\}\}/.test(vorlage)) return false
|
|
const klein = vorlage.toLowerCase()
|
|
const nennBestellung =
|
|
klein.includes('beschluss') && (klein.includes('betreuer') || klein.includes('betreuung'))
|
|
return !nennBestellung
|
|
}
|