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
105 lines
3.1 KiB
TypeScript
105 lines
3.1 KiB
TypeScript
import 'server-only'
|
|
import * as React from 'react'
|
|
import { Document, Page, StyleSheet, Text, View } from '@react-pdf/renderer'
|
|
import { fuerStandardschrift, pdfErzeugen } from '@/lib/pdf/erzeugen'
|
|
|
|
/**
|
|
* Das Schreiben als PDF - die Fassung, die in die Akte geht.
|
|
*
|
|
* Warum PDF und nicht die Word-Datei?
|
|
*
|
|
* Erstens der Ablageschutz: src/lib/speicher/dateityp.ts laesst nur PDF und
|
|
* Bilder in den Objektspeicher, und zwar nach den Magic Bytes, nicht nach
|
|
* Endung oder gemeldetem Typ. Eine .docx ist ein ZIP-Archiv; sie
|
|
* durchzulassen hiesse, jedes ZIP durchzulassen. Diese Pruefung ist eine
|
|
* bewusste Zusage der Anwendung, und sie fuer eine Bequemlichkeit
|
|
* aufzuweichen waere der falsche Handel.
|
|
*
|
|
* Zweitens die Sache selbst: In der Akte liegt der Nachweis, was
|
|
* hinausgegangen ist. Dafuer ist ein Format, das sich nicht mehr aendert,
|
|
* das richtige. Die .docx ist die Arbeitsfassung zum Ausdrucken und
|
|
* Unterschreiben - sie liegt beim Benutzer, nicht in der Akte.
|
|
*
|
|
* Zu beachten: Abgelegt wird der Text, wie er beim Ablegen im Browser
|
|
* stand. Wer die Word-Datei danach noch aendert, hat in der Akte eine
|
|
* andere Fassung als im Briefkasten. Die Oberflaeche sagt das dazu.
|
|
*/
|
|
|
|
const stile = StyleSheet.create({
|
|
seite: {
|
|
paddingTop: 70,
|
|
paddingBottom: 56,
|
|
paddingLeft: 71, // 25 mm - da wird gelocht
|
|
paddingRight: 57,
|
|
fontSize: 10.5,
|
|
lineHeight: 1.45,
|
|
fontFamily: 'Helvetica',
|
|
color: '#111111',
|
|
},
|
|
zeile: { marginBottom: 0 },
|
|
leer: { height: 9 },
|
|
betreff: { fontFamily: 'Helvetica-Bold', marginBottom: 4 },
|
|
datum: { textAlign: 'right' },
|
|
fuss: {
|
|
position: 'absolute',
|
|
bottom: 28,
|
|
left: 71,
|
|
right: 57,
|
|
fontSize: 7.5,
|
|
color: '#777777',
|
|
textAlign: 'center',
|
|
},
|
|
})
|
|
|
|
function istBetreff(zeile: string): boolean {
|
|
return /^Betreff:/i.test(zeile.trim())
|
|
}
|
|
|
|
function istDatumszeile(zeile: string): boolean {
|
|
return /^([A-Za-zÄÖÜäöüß .-]+,\s*)?\d{1,2}\.\d{1,2}\.\d{4}$/.test(zeile.trim())
|
|
}
|
|
|
|
export function Briefdokument({
|
|
text,
|
|
erzeugtAm,
|
|
}: {
|
|
text: string
|
|
erzeugtAm: Date
|
|
}): React.JSX.Element {
|
|
const zeilen = text.split('\n')
|
|
|
|
return (
|
|
<Document>
|
|
<Page size="A4" style={stile.seite}>
|
|
{zeilen.map((zeile, i) => {
|
|
if (zeile.trim() === '') return <View key={i} style={stile.leer} />
|
|
|
|
const stil = istBetreff(zeile)
|
|
? [stile.zeile, stile.betreff]
|
|
: istDatumszeile(zeile)
|
|
? [stile.zeile, stile.datum]
|
|
: stile.zeile
|
|
|
|
return (
|
|
<Text key={i} style={stil}>
|
|
{fuerStandardschrift(zeile)}
|
|
</Text>
|
|
)
|
|
})}
|
|
|
|
<Text style={stile.fuss} fixed>
|
|
{fuerStandardschrift(
|
|
`Aus dem Betreuungs-Cockpit erzeugt am ${erzeugtAm.toLocaleDateString('de-DE')}. ` +
|
|
'Diese Fassung liegt in der Akte.',
|
|
)}
|
|
</Text>
|
|
</Page>
|
|
</Document>
|
|
)
|
|
}
|
|
|
|
/** Fertiges PDF als Buffer, fuer die Ablage im Objektspeicher. */
|
|
export async function briefPdfErzeugen(text: string, erzeugtAm: Date): Promise<Buffer> {
|
|
return pdfErzeugen(<Briefdokument text={text} erzeugtAm={erzeugtAm} />)
|
|
}
|