Die Anschreiben gab es bisher nur als Text zum Kopieren. Danach war
Handarbeit: in Word nachbauen, ausdrucken, abschicken - und noch einmal
von Hand als Dokument in die Akte legen. Der letzte Schritt wurde am
haeufigsten vergessen, und dann fehlt der Nachweis im Bericht.
Neu unter /schreiben/neu: bearbeiten, als Word-Datei herunterladen, in
die Akte legen, Wiedervorlage setzen - in einem Zug. Eine Seite fuer alle
Arten und alle drei Empfaengerwege, statt die Bearbeitung dreimal an
Vertrag, Vorgang und Freitext nachzubauen.
Der dritte Weg ist der eigentliche Zugewinn: Schreiben an eine Stelle,
zu der es noch keinen Datensatz gibt. Am Anfang einer Betreuung ist das
der Normalfall - man fragt ja an, um zu erfahren, ob dort ueberhaupt
etwas besteht. Aus der freien Anschrift laesst sich mit einem Klick ein
Kontakt anlegen.
Zwei Entscheidungen, die erklaert gehoeren:
Die Akte bekommt ein PDF, heruntergeladen wird .docx. Der
Objektspeicher nimmt nur PDF und Bilder an, geprueft an den Magic Bytes.
Eine .docx ist ein ZIP-Archiv; sie durchzulassen hiesse jedes ZIP
durchzulassen, und diese Pruefung ist eine bewusste Zusage der
Anwendung. Sachlich ist PDF fuer den Nachweis ohnehin richtiger: Es
aendert sich nicht mehr. Die Oberflaeche sagt dazu, dass eine spaeter in
Word geaenderte Fassung von der Akte abweicht.
Der Berichtssatz in berichte/docx.ts bleibt unangetastet. Er setzt einen
Bericht mit Kopfzeilen und Unterschriftenblock; ein Brief braucht
Absender, Anschriftenfeld, Datumszeile und Grussformel. Beides in eine
Funktion zu zwingen haette beide Ausgaben schlechter gemacht. Der
Umsetzungshinweis im Issue ("muss nur angebunden werden") war insoweit
falsch - von mir selbst geschrieben.
Die vorhandenen Erzeuger (Rueckkaufswert, Kuendigung, Sachstandsanfrage)
bleiben unveraendert; ihr Wortlaut ist getestet und erprobt.
Geprueft: zehn Einheitstests, darunter der Gleichschritt der
Wiedervorlagefristen ueber zwei Module hinweg. Fuenf End-to-End-Tests
auf beiden Geraeteprofilen: Entwurf, Vertragsbezug, Word-Download,
Ablage samt Wiedervorlage, Kontakt aus freier Anschrift.
Refs #4
119 lines
3.8 KiB
TypeScript
119 lines
3.8 KiB
TypeScript
import 'server-only'
|
|
import {
|
|
AlignmentType,
|
|
Document,
|
|
Packer,
|
|
Paragraph,
|
|
TextRun,
|
|
convertMillimetersToTwip,
|
|
} from 'docx'
|
|
|
|
/**
|
|
* Ein Brief als Word-Datei.
|
|
*
|
|
* Getrennt von src/lib/berichte/docx.ts, und das ist Absicht. Der dortige
|
|
* Satz baut einen BERICHT: Titel, Kopfzeilen mit betreuter Person,
|
|
* Aktenzeichen und Berichtszeitraum, am Ende ein Unterschriftenblock. Ein
|
|
* Brief braucht etwas anderes - Absender, Anschriftenfeld, Datumszeile
|
|
* rechts, Betreff, Anrede, Grussformel. Beides in eine Funktion zu zwingen
|
|
* haette beide Ausgaben schlechter gemacht.
|
|
*
|
|
* Warum ueberhaupt .docx und nicht nur PDF? Weil jeder Fall eine
|
|
* Besonderheit hat, die in den Brief gehoert. Wer das Schreiben vor dem
|
|
* Absenden noch anfassen will, braucht eine Datei, die sich anfassen
|
|
* laesst. Die Fassung fuer die Akte ist ein PDF - siehe
|
|
* src/lib/schreiben/pdf.tsx.
|
|
*
|
|
* Das Layout folgt DIN 5008 lose: Anschriftenfeld oben links, Datum
|
|
* rechts, Betreff fett. Nicht auf den Millimeter - der Brief geht in einen
|
|
* gewoehnlichen Umschlag, nicht in ein Fensterkuvert der Behoerde.
|
|
*/
|
|
|
|
const SCHRIFT = 'Arial'
|
|
const GROESSE = 11
|
|
|
|
/**
|
|
* Der Entwurfstext kommt als eine Zeichenkette mit Zeilenumbruechen.
|
|
*
|
|
* Er ist bereits vollstaendig gesetzt: Absender, Anschrift, Datum,
|
|
* Betreff, Anrede, Text, Gruss. Diese Funktion formatiert ihn nur - sie
|
|
* denkt sich nichts dazu und laesst nichts weg. Was der Benutzer im
|
|
* Browser gesehen hat, steht auch in der Datei.
|
|
*/
|
|
function absatz(
|
|
text: string,
|
|
optionen: { fett?: boolean; rechts?: boolean; abstandNach?: number } = {},
|
|
): Paragraph {
|
|
return new Paragraph({
|
|
alignment: optionen.rechts ? AlignmentType.RIGHT : AlignmentType.LEFT,
|
|
spacing: { after: optionen.abstandNach ?? 0, line: 276 },
|
|
children: [
|
|
new TextRun({
|
|
text,
|
|
bold: optionen.fett ?? false,
|
|
size: GROESSE * 2, // docx rechnet in halben Punkten
|
|
font: SCHRIFT,
|
|
}),
|
|
],
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Erkennt die Betreffzeile, damit sie fett gesetzt werden kann.
|
|
*
|
|
* Die erzeugten Schreiben beginnen sie mit "Betreff:" - siehe
|
|
* betreffZeile() in src/lib/vertraege/anschreiben.ts. Steht dort einmal
|
|
* etwas anderes, wird die Zeile eben normal gesetzt; ein Brief mit
|
|
* unfettem Betreff ist immer noch ein brauchbarer Brief.
|
|
*/
|
|
function istBetreff(zeile: string): boolean {
|
|
return /^Betreff:/i.test(zeile.trim())
|
|
}
|
|
|
|
/**
|
|
* Die Datumszeile steht rechts, wie im deutschen Geschaeftsbrief.
|
|
* Erkennbar daran, dass sie mit einem Ort und Komma oder direkt mit einem
|
|
* Datum beginnt und sonst nichts enthaelt.
|
|
*/
|
|
function istDatumszeile(zeile: string): boolean {
|
|
return /^([A-Za-zÄÖÜäöüß .-]+,\s*)?\d{1,2}\.\d{1,2}\.\d{4}$/.test(zeile.trim())
|
|
}
|
|
|
|
export function briefAlsDocx(text: string): Document {
|
|
const zeilen = text.split('\n')
|
|
|
|
const inhalt: Paragraph[] = zeilen.map((zeile) => {
|
|
if (zeile.trim() === '') return absatz('', { abstandNach: 120 })
|
|
if (istBetreff(zeile)) return absatz(zeile, { fett: true, abstandNach: 120 })
|
|
if (istDatumszeile(zeile)) return absatz(zeile, { rechts: true })
|
|
return absatz(zeile)
|
|
})
|
|
|
|
return new Document({
|
|
creator: 'Betreuungs-Cockpit',
|
|
description: 'Schreiben aus der Betreuungsakte',
|
|
title: 'Schreiben',
|
|
sections: [
|
|
{
|
|
properties: {
|
|
page: {
|
|
margin: {
|
|
top: convertMillimetersToTwip(25),
|
|
right: convertMillimetersToTwip(20),
|
|
// Links etwas mehr: Da wird gelocht und abgeheftet.
|
|
left: convertMillimetersToTwip(25),
|
|
bottom: convertMillimetersToTwip(20),
|
|
},
|
|
},
|
|
},
|
|
children: inhalt,
|
|
},
|
|
],
|
|
})
|
|
}
|
|
|
|
/** Fertige .docx-Datei als Buffer. */
|
|
export async function briefDocxErzeugen(text: string): Promise<Buffer> {
|
|
return Packer.toBuffer(briefAlsDocx(text))
|
|
}
|