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
187 lines
6.7 KiB
TypeScript
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>
|
|
)
|
|
}
|