import { notFound } from 'next/navigation' import Link from 'next/link' import { ArrowLeft, FileDown, Clock } from 'lucide-react' import type { VorgangStatus } from '@prisma/client' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { SeitenKopf } from '@/components/seiten-kopf' import { AufgabenListe, type AufgabenZeile } from '@/components/vorgaenge/aufgaben-liste' import { Faelligkeit } from '@/components/vorgaenge/faelligkeit' import { GenehmigungAnlegen } from '@/components/vorgaenge/genehmigung-anlegen' import { GenehmigungsKette, type GenehmigungsAnzeige, } from '@/components/vorgaenge/genehmigungs-kette' import { SachstandsanfrageKnopf } from '@/components/vorgaenge/sachstandsanfrage-knopf' import { StatusWechsler } from '@/components/vorgaenge/status-wechsler' import { VorgangLoeschen } from '@/components/vorgaenge/vorgang-loeschen' import { VorgangsFormular } from '@/components/vorgaenge/vorgangs-formular' import { ZeitstrahlAnzeige } from '@/components/vorgaenge/zeitstrahl-anzeige' import { aktiveBetreute, erfordereBenutzer, erfordereZugang } from '@/lib/auth/sitzung' import { centZuZahl, db, nichtGeloescht } from '@/lib/db' import { entschluesselnWennMoeglich, oeffnen } from '@/lib/crypto/feld' import { KATEGORIE_BESCHRIFTUNG, PRIORITAET_BESCHRIFTUNG } from '@/lib/validierung/vorgaenge' import { sachstandsanfrageErzeugen } from '@/lib/vorgaenge/sachstandsanfrage' import { VORGANG_STATUS_BESCHRIFTUNG, zeitstrahlBauen, type ZeitstrahlStatuswechsel, } from '@/lib/vorgaenge/zeitstrahl' import { formatDatum } from '@/lib/utils' /** * Die Fallakte. * * Kopf mit Status, Gegenstelle, Aktenzeichen und dem nächsten Handgriff; * darunter die Aufgabenliste, das Genehmigungsverfahren und der * Zeitstrahl, der alles zusammenführt, was in dieser Sache passiert ist. */ export default async function VorgangSeite({ params }: { params: Promise<{ id: string }> }) { const { id } = await params const benutzer = await erfordereBenutzer(`/vorgaenge/${id}`) const aktive = await aktiveBetreute(benutzer) if (!aktive) notFound() const vorgang = await db.vorgang.findFirst({ where: { id, betreuteId: aktive.id, ...nichtGeloescht }, include: { aufgaben: { orderBy: [{ reihenfolge: 'asc' }, { erstelltAm: 'asc' }] }, genehmigungen: { where: nichtGeloescht, include: { beschlussDokument: { select: { id: true, titel: true } } }, orderBy: { erstelltAm: 'asc' }, }, fristen: { where: nichtGeloescht, orderBy: { datum: 'asc' } }, buchungen: { where: nichtGeloescht, orderBy: { datum: 'desc' } }, kontaktprotokolle: { where: nichtGeloescht, orderBy: { datum: 'desc' } }, verknuepfungen: { include: { dokument: true } }, }, }) if (!vorgang) notFound() const { darfSchreiben } = await erfordereZugang(aktive.id, 'VORGAENGE', 'lesen') const schreiben = darfSchreiben('VORGAENGE') const offen = oeffnen('Vorgang', vorgang) // --- Statuswechsel aus dem Audit-Log -------------------------------------- // Das Log ist ohnehin der Nachweis gegenueber dem Gericht; daraus die // Statuschronik zu lesen, spart eine zusaetzliche Tabelle. const protokolle = await db.auditLog.findMany({ where: { entitaet: 'Vorgang', entitaetId: vorgang.id, aktion: 'GEAENDERT' }, orderBy: { zeitpunkt: 'asc' }, take: 200, }) const statuswechsel: ZeitstrahlStatuswechsel[] = [] for (const eintrag of protokolle) { const roh = entschluesselnWennMoeglich( eintrag.aenderungVerschluesselt, 'aenderungVerschluesselt', ) if (!roh) continue try { const gelesen = JSON.parse(roh) as Record const status = gelesen.status if (!status || typeof status.neu !== 'string') continue statuswechsel.push({ id: eintrag.id, zeitpunkt: eintrag.zeitpunkt, von: typeof status.alt === 'string' ? (status.alt as VorgangStatus) : null, nach: status.neu as VorgangStatus, wer: eintrag.userName, }) } catch { // Ein unlesbarer Protokolleintrag darf die Akte nicht blockieren. continue } } // --- Zeitstrahl ----------------------------------------------------------- const zeitstrahl = zeitstrahlBauen({ vorgang: { titel: vorgang.titel, erstelltAm: vorgang.erstelltAm, erledigtAm: vorgang.erledigtAm, }, dokumente: vorgang.verknuepfungen .map((v) => v.dokument) .filter((d) => d.geloeschtAm === null) .map((d) => ({ id: d.id, titel: d.titel, eingangAm: d.eingangAm, versandAm: d.versandAm, erstelltAm: d.erstelltAm, absender: d.absender, empfaenger: d.empfaenger, })), kontaktprotokolle: vorgang.kontaktprotokolle.map((p) => { const klar = oeffnen('Kontaktprotokoll', p) return { id: p.id, datum: p.datum, art: p.art, gespraechspartner: p.gespraechspartner, mitBetreuter: p.mitBetreuter, inhalt: klar.inhalt ?? null, } }), fristen: vorgang.fristen.map((f) => ({ id: f.id, titel: f.titel, datum: f.datum, erledigtAm: f.erledigtAm, ausschlussfrist: f.ausschlussfrist, rechtsgrundlage: f.rechtsgrundlage, })), buchungen: vorgang.buchungen.map((b) => { const klar = oeffnen('Buchung', b) return { id: b.id, datum: b.datum, betragCent: centZuZahl(b.betragCent) ?? 0, richtung: b.richtung, gegenstelle: b.gegenstelle, verwendungszweck: klar.verwendungszweck ?? null, } }), genehmigungen: vorgang.genehmigungen.map((g) => ({ id: g.id, titel: g.titel, rechtsgrundlage: g.rechtsgrundlage, status: g.status, mitteilungAm: g.mitteilungAm, antragAm: g.antragAm, anhoerungAm: g.anhoerungAm, beschlussAm: g.beschlussAm, rechtskraftAm: g.rechtskraftAm, vollzugAm: g.vollzugAm, })), statuswechsel, }) // --- Sachstandsanfrage ---------------------------------------------------- // Nur, wenn tatsaechlich auf jemanden gewartet wird - sonst waere der // Knopf nur Zierde. const wartetAufDritte = vorgang.status === 'WARTET_AUF_DRITTE' const anfrage = wartetAufDritte ? sachstandsanfrageErzeugen({ vorgangTitel: vorgang.titel, gegenstelle: vorgang.gegenstelle, aktenzeichen: vorgang.aktenzeichen, letzterKontaktAm: vorgang.letzterKontaktAm, betreute: { vorname: aktive.vorname, nachname: aktive.nachname, geburtsdatum: aktive.geburtsdatum, }, betreuer: { name: benutzer.name, aktenzeichenGericht: aktive.aktenzeichenGericht, betreuungsgericht: aktive.betreuungsgericht, }, }) : null // Dokumente der betreuten Person - Auswahl fuer das Beschlussdokument. const dokumente = vorgang.genehmigungen.length ? await db.dokument.findMany({ where: { betreuteId: aktive.id, ...nichtGeloescht }, select: { id: true, titel: true }, orderBy: { erstelltAm: 'desc' }, take: 100, }) : [] const aufgaben: AufgabenZeile[] = vorgang.aufgaben.map((a) => ({ id: a.id, titel: a.titel, beschreibung: a.beschreibung, erledigt: a.erledigt, faelligAm: a.faelligAm, })) const genehmigungen: GenehmigungsAnzeige[] = vorgang.genehmigungen.map((g) => ({ id: g.id, titel: g.titel, rechtsgrundlage: g.rechtsgrundlage, status: g.status, mitteilungAm: g.mitteilungAm, antragAm: g.antragAm, anhoerungAm: g.anhoerungAm, beschlussAm: g.beschlussAm, rechtskraftAm: g.rechtskraftAm, vollzugAm: g.vollzugAm, beschlussDokumentId: g.beschlussDokumentId, beschlussDokumentTitel: g.beschlussDokument?.titel ?? null, dokumente, })) return (
{anfrage && ( )} {schreiben && ( )} {schreiben && } } /> {/* --- Kopf der Akte ---------------------------------------------- */}

Status

{VORGANG_STATUS_BESCHRIFTUNG[vorgang.status]}

{schreiben && ( )}

Gegenstelle

{vorgang.gegenstelle ?? '–'}

{vorgang.letzterKontaktAm && (

)}

Aktenzeichen

{vorgang.aktenzeichen ?? '–'}

Nächster Schritt

{vorgang.naechsterSchritt ?? 'Kein offener Handgriff notiert.'}

Einordnung

{KATEGORIE_BESCHRIFTUNG[vorgang.kategorie]} {PRIORITAET_BESCHRIFTUNG[vorgang.prioritaet] ?? 'Normal'}
{/* --- Aufgaben ----------------------------------------------------- */}

Aufgaben

{/* --- Genehmigungen ------------------------------------------------ */} {(vorgang.genehmigungspflichtig || genehmigungen.length > 0) && (

Genehmigung des Betreuungsgerichts

{genehmigungen.map((genehmigung) => ( ))} {schreiben && genehmigungen.length === 0 && ( )}
)} {/* --- Zeitstrahl --------------------------------------------------- */}

Zeitstrahl

Dokumente, Gespräche, Fristen, Zahlungen und Statuswechsel in einer Spalte - neueste zuerst.

{/* --- Bearbeiten --------------------------------------------------- */} {schreiben && (
Angaben bearbeiten
)}
) }