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
130 lines
4.5 KiB
TypeScript
130 lines
4.5 KiB
TypeScript
import type { Metadata } from 'next'
|
|
import { redirect } from 'next/navigation'
|
|
import { Alert, AlertDescription } from '@/components/ui/alert'
|
|
import { AlertCircle, Scale } from 'lucide-react'
|
|
import { SeitenKopf } from '@/components/seiten-kopf'
|
|
import { SchreibenEditor } from '@/components/schreiben/schreiben-editor'
|
|
import { aktiveBetreute, erfordereBenutzer, erfordereZugang } from '@/lib/auth/sitzung'
|
|
import { db, nichtGeloescht } from '@/lib/db'
|
|
import { entwurfBauen } from '@/lib/schreiben/entwurf'
|
|
import { SCHREIBEN_EIGENSCHAFTEN, istSchreibenArt } from '@/lib/schreiben/arten'
|
|
|
|
export const metadata: Metadata = { title: 'Schreiben' }
|
|
export const dynamic = 'force-dynamic'
|
|
|
|
/**
|
|
* Ein Schreiben erzeugen, bearbeiten, ausgeben und ablegen.
|
|
*
|
|
* Eine Seite für alle Arten und alle drei Empfängerwege — statt die
|
|
* Bearbeitung dreimal an Vertrag, Vorgang und Freitext nachzubauen. Die
|
|
* bestehenden Seiten verlinken hierher.
|
|
*
|
|
* Der Fall ohne Bezug ist ausdrücklich vorgesehen: Am Anfang einer
|
|
* Betreuung schreibt man an Stellen, zu denen es noch keinen Datensatz
|
|
* gibt — man fragt ja gerade an, um zu erfahren, ob dort etwas besteht.
|
|
*/
|
|
export default async function SchreibenSeite({
|
|
searchParams,
|
|
}: {
|
|
searchParams: Promise<Record<string, string | string[] | undefined>>
|
|
}) {
|
|
const parameter = await searchParams
|
|
const benutzer = await erfordereBenutzer('/schreiben/neu')
|
|
const aktive = await aktiveBetreute(benutzer)
|
|
if (!aktive) redirect('/ersteinrichtung')
|
|
|
|
const { darfSchreiben } = await erfordereZugang(aktive.id, 'DOKUMENTE', 'lesen')
|
|
if (!darfSchreiben('DOKUMENTE')) redirect('/dokumente')
|
|
|
|
const text = (name: string): string => {
|
|
const wert = parameter[name]
|
|
return typeof wert === 'string' ? wert.trim() : ''
|
|
}
|
|
|
|
const rohArt = text('art')
|
|
const art = istSchreibenArt(rohArt) ? rohArt : 'FREI'
|
|
const eigenschaften = SCHREIBEN_EIGENSCHAFTEN[art]
|
|
|
|
let entwurf
|
|
try {
|
|
entwurf = await entwurfBauen({
|
|
betreuteId: aktive.id,
|
|
benutzerName: benutzer.name,
|
|
art,
|
|
vertragId: text('vertragId') || null,
|
|
vorgangId: text('vorgangId') || null,
|
|
})
|
|
} catch (ausnahme) {
|
|
// Fehlender oder fremder Bezug: Das ist kein Absturz, sondern eine
|
|
// Angabe, die nicht stimmt. Die Seite sagt es und bietet den freien
|
|
// Weg an, statt den Benutzer vor eine Fehlerseite zu setzen.
|
|
const meldung =
|
|
ausnahme instanceof Error ? ausnahme.message : 'Der Entwurf ließ sich nicht bauen.'
|
|
return (
|
|
<div className="max-w-3xl space-y-6">
|
|
<SeitenKopf titel="Schreiben" beschreibung={eigenschaften.beschriftung} />
|
|
<Alert variant="fehler">
|
|
<AlertCircle aria-hidden="true" />
|
|
<AlertDescription>
|
|
{meldung} Du kannst stattdessen ein{' '}
|
|
<a href="/schreiben/neu?art=FREI" className="underline">
|
|
freies Schreiben
|
|
</a>{' '}
|
|
erzeugen.
|
|
</AlertDescription>
|
|
</Alert>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
const kontakte = await db.kontakt.findMany({
|
|
where: { betreuteId: aktive.id, ...nichtGeloescht },
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
organisation: true,
|
|
strasse: true,
|
|
plz: true,
|
|
ort: true,
|
|
email: true,
|
|
},
|
|
orderBy: [{ organisation: 'asc' }, { name: 'asc' }],
|
|
take: 200,
|
|
})
|
|
|
|
return (
|
|
<div className="max-w-3xl space-y-6">
|
|
<SeitenKopf
|
|
titel={eigenschaften.beschriftung}
|
|
beschreibung="Bearbeiten, als Word-Datei herunterladen, in die Akte legen — und eine Wiedervorlage setzen, damit das Warten nicht vergessen geht."
|
|
pfad={[{ titel: 'Dokumente', href: '/dokumente' }, { titel: 'Schreiben' }]}
|
|
/>
|
|
|
|
<SchreibenEditor
|
|
betreuteId={aktive.id}
|
|
art={art}
|
|
entwurf={{
|
|
betreff: entwurf.betreff,
|
|
text: entwurf.text,
|
|
empfaengerName: entwurf.empfaengerName,
|
|
wiedervorlageAm: entwurf.wiedervorlageAm,
|
|
}}
|
|
kontakte={kontakte}
|
|
vertragId={entwurf.vertragId}
|
|
vorgangId={entwurf.vorgangId}
|
|
fristBegruendung={eigenschaften.fristBegruendung}
|
|
warnungen={entwurf.warnungen}
|
|
/>
|
|
|
|
<Alert variant="hinweis">
|
|
<Scale aria-hidden="true" />
|
|
<AlertDescription>
|
|
Der vorgeschlagene Wortlaut ist ein Entwurf, keine Rechtsberatung. Bei einem
|
|
genehmigungspflichtigen Geschäft gehört die Genehmigung des Betreuungsgerichts vor den
|
|
Versand — nicht danach.
|
|
</AlertDescription>
|
|
</Alert>
|
|
</div>
|
|
)
|
|
}
|