feat: Jahresbericht, Vollexport, Wissensbasis und Mitleser-Freigaben
Etappe 7. Die Ausgabeschicht - alles, was diese Anwendung nach aussen abgibt. Der Jahresbericht nach § 1863 BGB ist ein ENTWURF zum Ueberarbeiten, kein fertiger Text. Er zieht sich die fuenf Gliederungspunkte aus Kontaktprotokollen, Gesundheitsereignissen und Vorgangsstatus zusammen; was davor abgegeben wird, entscheidet der Betreuer. Das sagt auch die Oberflaeche. Ausgabe als PDF und als DOCX, damit sich weiterschreiben laesst. Vollexport als ZIP-Strom mit Gegendruck, damit auch grosse Bestaende ohne Speicherausbruch durchgehen: Originaldateien nach Jahren sortiert, alle Tabellen als entschluesseltes JSON, die erzeugten Berichte, ein LIESMICH und SHA-256 je Datei. Damit laesst sich die Anwendung verlassen - kein Datensilo. Der Export ist eine sensible Aktion: nur BETREUER und ADMIN, nur nach frischer Anmeldung, immer mit Audit-Eintrag. Ein Mitleser kommt nicht heran. 37 Hinweiskarten in daten/hinweise.json, Zod-geprueft und ohne Neustart pflegbar. Jede mit Rechtsgrundlage, Link und dem stehenden Vermerk, dass sie keine Rechtsberatung ersetzt. Modulweise Freigaben fuer Mitleser, mit ausdruecklicher Warnung bei Gesundheit, Finanzen und Sozialhilfe. Rollen- und Freigabeaenderungen erfordern eine frische Anmeldung und stehen im Audit-Log. Nebenbei geloest: Webpack loest .tsx vor .ts auf, TypeScript und Vitest umgekehrt. Ein Modul, das beide Endungen traegt, importiert sich unter einem der beiden Werkzeuge selbst. Deshalb heissen die Bausteine jetzt eindeutig (jahresbericht.entwurf.ts, .inhalt.tsx, .ausgabe.ts) und werden ueber eine gemeinsame Fassade weitergereicht. 41 Tests. Etappe: 7 Phase: Ausgabe
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
import Link from 'next/link'
|
||||
import { SeitenKopf, Leerzustand } from '@/components/seiten-kopf'
|
||||
import { Rechtshinweis } from '@/components/rechtshinweis'
|
||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { UnterlagenpaketFormular } from '@/components/berichte/unterlagenpaket-formular'
|
||||
import { berichtskontext } from '@/components/berichte/kontext'
|
||||
import { HinweisKarten } from '@/components/hinweise/HinweisKarten'
|
||||
import { erfordereZugang } from '@/lib/auth/sitzung'
|
||||
import { db, nichtGeloescht } from '@/lib/db'
|
||||
|
||||
export const metadata = { title: 'Unterlagen für den Träger' }
|
||||
|
||||
/**
|
||||
* Das Unterlagenpaket fuer den Sozialhilfetraeger.
|
||||
*
|
||||
* Ausgangspunkt ist der Nachforderungs-Tracker: Was der Bezirk angefordert
|
||||
* hat, steht dort als eigene Zeile. Hier wird ausgewaehlt, was in dieses
|
||||
* Paket kommt - auch das, was es nicht gibt, denn eine unbeantwortete
|
||||
* Position gilt beim Traeger als fehlende Mitwirkung.
|
||||
*/
|
||||
export default async function UnterlagenpaketSeite({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Promise<Record<string, string | string[] | undefined>>
|
||||
}) {
|
||||
const { betreuteId, darfBerichteSchreiben } = await berichtskontext('lesen')
|
||||
// Die Unterlagen stammen aus der Sozialhilfeakte - auch dort muss gelesen
|
||||
// werden duerfen, sonst umginge diese Seite die Modulfreigabe.
|
||||
await erfordereZugang(betreuteId, 'SOZIALHILFE', 'lesen')
|
||||
|
||||
const parameter = await searchParams
|
||||
const gewaehlterAntrag = Array.isArray(parameter.antrag) ? parameter.antrag[0] : parameter.antrag
|
||||
|
||||
const antraege = await db.sozialhilfeAntrag.findMany({
|
||||
where: { betreuteId, ...nichtGeloescht },
|
||||
orderBy: { erstelltAm: 'desc' },
|
||||
include: {
|
||||
nachforderungen: {
|
||||
where: nichtGeloescht,
|
||||
orderBy: [{ reihenfolge: 'asc' }, { bezeichnung: 'asc' }],
|
||||
include: { verknuepfungen: { select: { dokumentId: true } } },
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const antrag = antraege.find((a) => a.id === gewaehlterAntrag) ?? antraege[0]
|
||||
|
||||
if (!antrag) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<SeitenKopf
|
||||
titel="Unterlagen für den Träger"
|
||||
pfad={[{ titel: 'Berichte', href: '/berichte' }, { titel: 'Unterlagenpaket' }]}
|
||||
/>
|
||||
<Leerzustand
|
||||
titel="Es ist noch kein Antrag erfasst"
|
||||
beschreibung="Das Unterlagenpaket baut auf dem Nachforderungs-Tracker auf. Lege zuerst den Antrag auf Hilfe zur Pflege an und trage ein, was der Träger angefordert hat."
|
||||
aktion={
|
||||
<Button asChild>
|
||||
<Link href="/sozialhilfe">Zur Sozialhilfe</Link>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const nachforderungen = antrag.nachforderungen.map((eintrag) => ({
|
||||
id: eintrag.id,
|
||||
bezeichnung: eintrag.bezeichnung,
|
||||
status: eintrag.status,
|
||||
fristBis: eintrag.fristBis ? eintrag.fristBis.toISOString() : null,
|
||||
begruendungNichtVorhanden: eintrag.begruendungNichtVorhanden,
|
||||
anzahlDokumente: eintrag.verknuepfungen.length,
|
||||
}))
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<SeitenKopf
|
||||
titel="Unterlagen für den Träger"
|
||||
pfad={[{ titel: 'Berichte', href: '/berichte' }, { titel: 'Unterlagenpaket' }]}
|
||||
beschreibung={`Ein PDF für ${antrag.traeger}: Anschreiben mit laufender Nummerierung, Inhaltsverzeichnis und die Unterlagen in genau dieser Reihenfolge.`}
|
||||
/>
|
||||
|
||||
{antraege.length > 1 && (
|
||||
<nav aria-label="Antrag wählen" className="flex flex-wrap gap-2">
|
||||
{antraege.map((eintrag) => (
|
||||
<Button
|
||||
key={eintrag.id}
|
||||
asChild
|
||||
size="sm"
|
||||
variant={eintrag.id === antrag.id ? 'default' : 'outline'}
|
||||
>
|
||||
<Link href={`/berichte/unterlagenpaket?antrag=${eintrag.id}`}>
|
||||
{eintrag.leistungsart}
|
||||
{eintrag.aktenzeichen ? ` · ${eintrag.aktenzeichen}` : ''}
|
||||
</Link>
|
||||
</Button>
|
||||
))}
|
||||
</nav>
|
||||
)}
|
||||
|
||||
<Alert variant="hinweis">
|
||||
<AlertTitle>Auch fehlende Unterlagen gehören ins Paket</AlertTitle>
|
||||
<AlertDescription>
|
||||
Zu jeder Position, die du mitschickst, aber nicht belegen kannst, erscheint im Anschreiben
|
||||
eine Erklärung. Das ist wichtig: Eine Position, zu der der Träger nichts hört, gilt als
|
||||
fehlende Mitwirkung – und das kann die Leistung kosten.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
{nachforderungen.length === 0 ? (
|
||||
<Leerzustand
|
||||
titel="Keine offenen Nachforderungen"
|
||||
beschreibung="Sobald der Träger Unterlagen anfordert und du sie im Nachforderungs-Tracker erfasst, kannst du hier ein Paket daraus bauen."
|
||||
aktion={
|
||||
<Button asChild>
|
||||
<Link href="/sozialhilfe">Zum Nachforderungs-Tracker</Link>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
) : darfBerichteSchreiben ? (
|
||||
<UnterlagenpaketFormular
|
||||
betreuteId={betreuteId}
|
||||
antragId={antrag.id}
|
||||
traeger={antrag.traeger}
|
||||
nachforderungen={nachforderungen}
|
||||
/>
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Du hast nur Leserechte für die Berichte und kannst deshalb kein Paket erzeugen.
|
||||
</p>
|
||||
)}
|
||||
|
||||
<HinweisKarten kontext={['SOZIALHILFE', 'VORGANG:SOZIALHILFE', 'DOKUMENTE']} />
|
||||
|
||||
<Rechtshinweis rechtsgrundlage="§ 60 SGB I, § 66 SGB I" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user