Files
betreuungs-cockpit/src/app/(app)/vorgaenge/neu/page.tsx
T
Florian Wilde 9e68d10787 Ansprechpartner am Vorgang hinterlegen
Ein namentlich adressiertes Schreiben landet bei der Person, die den
Vorgang kennt, statt in der Poststelle. Am Vorgang laesst sich jetzt ein
Kontakt aus dem Verzeichnis als Ansprechpartner benennen; die
Sachstandsanfrage redet ihn an, und sein Name steht ueber der Behoerde.

Die Sachstandsanfrage konnte das schon - sie bekam den Namen nur nie
uebergeben. entwurf.ts lud einen der verknuepften Kontakte und verwarf
ihn wieder. Wer angeredet wird, ist jetzt eine Entscheidung und kein
Zufall.

Aufbau:
- Migration: ansprechpartnerId am Vorgang (SetNull - ein Vorgang darf
  nicht daran haengen, dass es den Kontakt noch gibt) und anrede am
  Kontakt.
- Die Anrede als reine Funktion in kontakte/anrede.ts. Geraten wird nie:
  ohne hinterlegte Anrede wird mit vollem Namen angeredet. Ein leerer
  Name mit gesetzter Anrede faellt auf die allgemeine Anrede zurueck -
  "Sehr geehrte Frau ," darf nie herausgehen.
- Der Wechsel des Ansprechpartners steht mit NAMEN im Protokoll. Zwei
  cuids nebeneinander waeren protokolliert, aber nicht nachvollziehbar,
  und nachvollziehbar war die Anforderung.
- Der Ansprechpartner wird gegen die Akte geprueft, bevor er gespeichert
  wird - sein Name steht anschliessend im Briefkopf.

Nebenbei behoben: Der Schreiben-Editor verwarf die empfaengerAnschrift
aus dem Entwurf, sein State startete immer leer. Jetzt kommt die
Anschrift des Ansprechpartners im Formular an.

Geprueft: neun Einheitstests fuer die Anrede, fuenf End-to-End-Tests von
der Auswahl bis zum Protokolleintrag.

Refs #9
2026-08-23 16:31:10 +02:00

187 lines
6.7 KiB
TypeScript

import Link from 'next/link'
import { ArrowLeft, ListChecks, Scale } from 'lucide-react'
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { SeitenKopf } from '@/components/seiten-kopf'
import { Rechtshinweis } from '@/components/rechtshinweis'
import { VorgangsFormular } from '@/components/vorgaenge/vorgangs-formular'
import { VorlagenKacheln } from '@/components/vorgaenge/vorlagen-kacheln'
import { aktiveBetreute, erfordereBenutzer, erfordereZugang } from '@/lib/auth/sitzung'
import { GENEHMIGUNG_WARNUNG } from '@/lib/vorgaenge/genehmigungskette'
import { vorlageFinden } from '@/lib/vorgaenge/vorlagen'
import { db, nichtGeloescht } from '@/lib/db'
import { formatDatum } from '@/lib/utils'
import { addDays } from 'date-fns'
/**
* Neuen Vorgang anlegen - erst die Vorlage wählen, dann die Angaben.
*
* Zwei Schritte statt einem: Die Vorlage entscheidet über die
* Aufgabenliste und darüber, ob ein Genehmigungsverfahren angelegt wird.
* Das soll man bewusst wählen, nicht nebenbei in einem Auswahlfeld.
*/
export default async function NeuerVorgangSeite({
searchParams,
}: {
searchParams: Promise<{ vorlage?: string; leer?: string }>
}) {
const benutzer = await erfordereBenutzer('/vorgaenge/neu')
const aktive = await aktiveBetreute(benutzer)
if (!aktive) return null
await erfordereZugang(aktive.id, 'VORGAENGE', 'schreiben')
// Kontakte dieser Akte fuer die Wahl des Ansprechpartners.
const kontakte = await db.kontakt.findMany({
where: { betreuteId: aktive.id, ...nichtGeloescht },
select: { id: true, name: true, organisation: true },
orderBy: [{ organisation: 'asc' }, { name: 'asc' }],
take: 300,
})
const auswahl = await searchParams
const vorlage = auswahl.vorlage ? vorlageFinden(auswahl.vorlage) : undefined
const leer = auswahl.leer === '1'
// Schritt 1: Kachelliste
if (!vorlage && !leer) {
return (
<div className="space-y-6">
<SeitenKopf
titel="Neuer Vorgang"
beschreibung="Wähle eine Vorlage - sie bringt die Aufgabenliste in der fachlich richtigen Reihenfolge mit. Oder fang leer an."
pfad={[
{ titel: 'Start', href: '/heute' },
{ titel: 'Vorgänge', href: '/vorgaenge' },
{ titel: 'Neu' },
]}
aktionen={
<Button variant="ghost" asChild>
<Link href="/vorgaenge">
<ArrowLeft aria-hidden="true" />
Zurück
</Link>
</Button>
}
/>
<VorlagenKacheln />
</div>
)
}
// Schritt 2: Angaben
const heute = new Date()
return (
<div className="space-y-6">
<SeitenKopf
titel={vorlage ? vorlage.titel : 'Leerer Vorgang'}
beschreibung={
vorlage
? vorlage.beschreibung
: 'Ohne vorbefüllte Aufgaben. Aufgaben lassen sich danach jederzeit ergänzen.'
}
pfad={[
{ titel: 'Start', href: '/heute' },
{ titel: 'Vorgänge', href: '/vorgaenge' },
{ titel: 'Neu', href: '/vorgaenge/neu' },
{ titel: vorlage ? vorlage.titel : 'Leer' },
]}
aktionen={
<Button variant="ghost" asChild>
<Link href="/vorgaenge/neu">
<ArrowLeft aria-hidden="true" />
Andere Vorlage
</Link>
</Button>
}
/>
{vorlage?.genehmigungspflichtig && (
<Alert variant="warnung">
<Scale aria-hidden="true" />
<AlertTitle>
Dieser Vorgang ist genehmigungspflichtig
{vorlage.rechtsgrundlage ? ` (${vorlage.rechtsgrundlage})` : ''}
</AlertTitle>
<AlertDescription>
{GENEHMIGUNG_WARNUNG} Die Genehmigungskette wird beim Anlegen gleich mit erzeugt.
</AlertDescription>
</Alert>
)}
{vorlage && (
<section className="space-y-3 rounded-lg border p-4" aria-labelledby="vorschau">
<h2 id="vorschau" className="flex items-center gap-2 text-base font-semibold">
<ListChecks className="size-4" aria-hidden="true" />
Diese Aufgaben werden angelegt
</h2>
<ol className="space-y-2">
{vorlage.aufgaben.map((aufgabe, index) => (
<li key={aufgabe.titel} className="flex gap-3 text-sm">
<span className="tabular-nums text-muted-foreground">{index + 1}.</span>
<span className="min-w-0 flex-1">
<span className="font-medium">{aufgabe.titel}</span>
{aufgabe.tageBisFaellig !== undefined && (
<span className="text-muted-foreground">
{' '}
· fällig {formatDatum(addDays(heute, aufgabe.tageBisFaellig))}
</span>
)}
</span>
</li>
))}
</ol>
{vorlage.fristen && vorlage.fristen.length > 0 && (
<div className="space-y-2 border-t pt-3">
<h3 className="text-sm font-semibold">Zusätzliche Fristen</h3>
<ul className="space-y-1">
{vorlage.fristen.map((frist) => (
<li key={frist.titel} className="text-sm">
{frist.titel}{' '}
<span className="text-muted-foreground">
· {formatDatum(addDays(heute, frist.tageBisFaellig))}
</span>
{frist.rechtsgrundlage && (
<Badge variant="outline" className="ml-2">
{frist.rechtsgrundlage}
</Badge>
)}
</li>
))}
</ul>
</div>
)}
{vorlage.rechtsgrundlage && <Rechtshinweis rechtsgrundlage={vorlage.rechtsgrundlage} />}
</section>
)}
<VorgangsFormular
modus="anlegen"
betreuteId={aktive.id}
vorlageSchluessel={vorlage?.schluessel ?? null}
abbrechenHref="/vorgaenge"
kontakte={kontakte}
werte={{
titel: vorlage?.titel ?? '',
kategorie: vorlage?.kategorie ?? 'SONSTIGES',
status: 'OFFEN',
prioritaet: 2,
beschreibung: vorlage?.beschreibung ?? null,
gegenstelle: null,
aktenzeichen: null,
ansprechpartnerId: null,
verantwortlich: benutzer.name,
naechsterSchritt: vorlage?.aufgaben[0]?.titel ?? null,
faelligAm: null,
letzterKontaktAm: null,
genehmigungspflichtig: vorlage?.genehmigungspflichtig ?? false,
}}
/>
</div>
)
}