import 'server-only' import { startOfDay } from 'date-fns' import type { Modul } from '@prisma/client' import { centZuZahl, db, nichtGeloescht } from '@/lib/db' import { fristenEinordnen, heimkostenlageBerechnen, nachforderungenAuswerten, tagAusDatenbank, tageBisTag, vermoegenBerechnen, wartendeEinordnen, type NachforderungRoh, } from '@/lib/dashboard/bewertung' import type { BelegeDaten, DashboardDaten, FristenAmpelDaten, KennzahlenDaten, NachforderungenDaten, PosteingangDaten, WartetAufAndereDaten, } from '@/lib/dashboard/typen' import { offeneZahlungsgruppen } from '@/lib/finanzen/wiederkehrend-laden' /** * Alles, was das Dashboard "Heute" braucht - in einem Rutsch. * * Die Abfragen laufen nebeneinander in einem Promise.all. Keine Schleife * fragt je Datensatz nach; wo eine Beziehung gebraucht wird (juengster * Wertstand je Vermoegensposten), holt Prisma sie in derselben Runde mit. * * Wer ein Modul nicht lesen darf, bekommt dafuer null zurueck statt * leerer Listen - die Kachel entfaellt dann ganz, statt faelschlich "0" * zu behaupten. */ /** Wie viele Zeilen zeigt eine Kachel hoechstens? Der Rest steht im Modul. */ const KACHEL_ZEILEN = 5 /** Sicherung gegen ausufernde Abfragen bei sehr langen Akten. */ const FRISTEN_MAX = 200 export type Lesepruefung = (modul: Modul) => boolean export async function dashboardLaden( betreute: { id: string; vorname: string; nachname: string }, darfLesen: Lesepruefung, jetzt: Date = new Date(), ): Promise { const heute = startOfDay(jetzt) const betreuteId = betreute.id const darfFristen = darfLesen('FRISTEN') const darfVorgaenge = darfLesen('VORGAENGE') const darfSozialhilfe = darfLesen('SOZIALHILFE') const darfDokumente = darfLesen('DOKUMENTE') const darfFinanzen = darfLesen('FINANZEN') const darfGesundheit = darfLesen('GESUNDHEIT') const [ fristenRoh, wartendRoh, nachforderungenRoh, posteingang, posteingangAnzahl, belegeOhneBuchung, belegeOhneBuchungGesamt, buchungenOhneBeleg, buchungenOhneBelegGesamt, postenRoh, heimkostenRoh, jahresberichtRoh, ] = await Promise.all([ // 1. Fristen - alle offenen, die Einordnung passiert danach im Speicher. darfFristen ? db.frist.findMany({ where: { betreuteId, status: 'OFFEN', ...nichtGeloescht }, orderBy: { datum: 'asc' }, take: FRISTEN_MAX, select: { id: true, titel: true, datum: true, typ: true, rechtsgrundlage: true, ausschlussfrist: true, vorgangId: true, }, }) : null, // 2. Vorgaenge, bei denen jemand anderes am Zug ist. darfVorgaenge ? db.vorgang.findMany({ where: { betreuteId, status: 'WARTET_AUF_DRITTE', ...nichtGeloescht }, orderBy: { letzterKontaktAm: 'asc' }, select: { id: true, titel: true, kategorie: true, gegenstelle: true, aktenzeichen: true, naechsterSchritt: true, letzterKontaktAm: true, erstelltAm: true, }, }) : null, // 3. Nachforderungen: alle Zeilen, damit "x von y" stimmt. darfSozialhilfe ? db.nachforderung.findMany({ where: { antrag: { betreuteId, ...nichtGeloescht }, ...nichtGeloescht }, orderBy: [{ reihenfolge: 'asc' }, { erstelltAm: 'asc' }], select: { id: true, bezeichnung: true, status: true, fristBis: true, reihenfolge: true, antragId: true, antrag: { select: { traeger: true, aktenzeichen: true } }, }, }) : null, // 4. Ungeprüfter Posteingang. darfDokumente ? db.dokument.findMany({ where: { betreuteId, posteingangStatus: 'NEU', ...nichtGeloescht }, orderBy: [{ eingangAm: 'desc' }, { erstelltAm: 'desc' }], take: KACHEL_ZEILEN, select: { id: true, titel: true, art: true, absender: true, eingangAm: true }, }) : null, darfDokumente ? db.dokument.count({ where: { betreuteId, posteingangStatus: 'NEU', ...nichtGeloescht }, }) : null, // 5a. Rechnungen und Quittungen, an denen keine Buchung haengt. darfDokumente && darfFinanzen ? db.dokument.findMany({ where: { betreuteId, art: { in: ['RECHNUNG', 'QUITTUNG'] }, belegFuer: { none: { geloeschtAm: null } }, ...nichtGeloescht, }, orderBy: [{ eingangAm: 'desc' }, { erstelltAm: 'desc' }], take: KACHEL_ZEILEN, select: { id: true, titel: true, art: true, eingangAm: true }, }) : null, darfDokumente && darfFinanzen ? db.dokument.count({ where: { betreuteId, art: { in: ['RECHNUNG', 'QUITTUNG'] }, belegFuer: { none: { geloeschtAm: null } }, ...nichtGeloescht, }, }) : null, // 5b. Buchungen ohne Beleg und ohne Begruendung - Belegzwang. darfFinanzen ? db.buchung.findMany({ where: { betreuteId, belegId: null, belegFehltBegruendung: null, ...nichtGeloescht }, orderBy: { datum: 'desc' }, take: KACHEL_ZEILEN, select: { id: true, datum: true, betragCent: true, richtung: true, kategorie: true, gegenstelle: true, }, }) : null, darfFinanzen ? db.buchung.count({ where: { betreuteId, belegId: null, belegFehltBegruendung: null, ...nichtGeloescht }, }) : null, // 6a. Vermoegen: je Posten der juengste Wertstand. darfFinanzen ? db.vermoegensposten.findMany({ where: { betreuteId, ...nichtGeloescht }, select: { id: true, art: true, wertstaende: { orderBy: { stichtag: 'desc' }, take: 1, select: { stichtag: true, wertCent: true }, }, }, }) : null, // 6b. Der zuletzt erfasste Heimkostenmonat. darfGesundheit ? db.heimkostenmonat.findFirst({ where: { betreuteId }, orderBy: [{ jahr: 'desc' }, { monat: 'desc' }], }) : null, // 6c. Der naechste Jahresbericht. darfFristen ? db.frist.findFirst({ where: { betreuteId, status: 'OFFEN', systemSchluessel: { startsWith: 'jahresbericht-' }, ...nichtGeloescht, }, orderBy: { datum: 'asc' }, select: { id: true, titel: true, datum: true }, }) : null, ]) // --- Einordnen ----------------------------------------------------------- const fristen: FristenAmpelDaten | null = fristenRoh ? fristenEinordnen(fristenRoh, heute) : null const wartet: WartetAufAndereDaten | null = wartendRoh ? (() => { const alle = wartendeEinordnen(wartendRoh, heute) return { ...alle, eintraege: alle.eintraege.slice(0, KACHEL_ZEILEN) } })() : null const nachforderungen: NachforderungenDaten | null = nachforderungenRoh ? nachforderungenAuswerten( nachforderungenRoh.map((zeile) => ({ id: zeile.id, bezeichnung: zeile.bezeichnung, status: zeile.status, fristBis: zeile.fristBis, reihenfolge: zeile.reihenfolge, antragId: zeile.antragId, antragTraeger: zeile.antrag.traeger, antragAktenzeichen: zeile.antrag.aktenzeichen, })), heute, ) : null const posteingangDaten: PosteingangDaten | null = posteingang && posteingangAnzahl !== null ? { anzahl: posteingangAnzahl, neueste: posteingang.map((d) => ({ id: d.id, titel: d.titel, art: d.art, absender: d.absender, eingangAm: d.eingangAm ? tagAusDatenbank(d.eingangAm) : null, })), } : null // Regelmaessige Abbuchungen ohne Vertrag. Eigene Abfrage, weil sie andere // Spalten braucht als der Belegzwang - und weil sie ganz entfaellt, wenn // das Finanzmodul nicht lesbar ist. const wiederkehrendOhneVertrag = darfFinanzen ? await offeneZahlungsgruppen(betreuteId) : 0 const belege: BelegeDaten | null = buchungenOhneBeleg && buchungenOhneBelegGesamt !== null ? { belegeOhneBuchung: (belegeOhneBuchung ?? []).map((d) => ({ id: d.id, titel: d.titel, art: d.art, eingangAm: d.eingangAm ? tagAusDatenbank(d.eingangAm) : null, })), belegeOhneBuchungGesamt: belegeOhneBuchungGesamt ?? 0, buchungenOhneBeleg: buchungenOhneBeleg.map((b) => ({ id: b.id, datum: tagAusDatenbank(b.datum), betragCent: centZuZahl(b.betragCent) ?? 0, richtung: b.richtung, kategorie: b.kategorie, gegenstelle: b.gegenstelle, })), buchungenOhneBelegGesamt, wiederkehrendOhneVertrag, } : null const vermoegenRoh = postenRoh ? vermoegenBerechnen(postenRoh) : null const heimkosten = heimkostenRoh ? heimkostenlageBerechnen(heimkostenRoh) : null const kennzahlen: KennzahlenDaten = { vermoegen: vermoegenRoh ? { stichtag: vermoegenRoh.stichtag, aktivaCent: centZuZahl(vermoegenRoh.aktivaCent) ?? 0, verbindlichkeitenCent: centZuZahl(vermoegenRoh.verbindlichkeitenCent) ?? 0, nettoCent: centZuZahl(vermoegenRoh.nettoCent) ?? 0, postenGesamt: vermoegenRoh.postenGesamt, postenOhneWert: vermoegenRoh.postenOhneWert, } : null, heimkosten: heimkosten ? { jahr: heimkosten.jahr, monat: heimkosten.monat, gesamtkostenCent: centZuZahl(heimkosten.gesamtkostenCent) ?? 0, deckungCent: centZuZahl(heimkosten.deckungCent) ?? 0, deckungsluecke: centZuZahl(heimkosten.deckungsluecke) ?? 0, } : null, jahresbericht: jahresberichtRoh ? { id: jahresberichtRoh.id, titel: jahresberichtRoh.titel, datum: tagAusDatenbank(jahresberichtRoh.datum), tage: tageBisTag(tagAusDatenbank(jahresberichtRoh.datum), heute), } : null, } return { betreuteId, name: `${betreute.vorname} ${betreute.nachname}`.trim(), heute, fristen, wartet, nachforderungen, posteingang: posteingangDaten, belege, kennzahlen, } }