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:
+285
-257
@@ -1,257 +1,285 @@
|
||||
'use server'
|
||||
|
||||
import { revalidatePath } from 'next/cache'
|
||||
import type { Modul } from '@prisma/client'
|
||||
import { db, papierkorbGrenze } from '@/lib/db'
|
||||
import { protokollieren } from '@/lib/audit'
|
||||
import { erfordereFrischeAnmeldung, erfordereZugang } from '@/lib/auth/sitzung'
|
||||
import { entfernen } from '@/lib/speicher'
|
||||
import { papierkorbSchema } from '@/lib/validierung/freigaben'
|
||||
|
||||
/**
|
||||
* Papierkorb.
|
||||
*
|
||||
* Loeschen heisst in dieser Anwendung: "geloeschtAm" setzen. Der Datensatz
|
||||
* bleibt 30 Tage sichtbar und wiederherstellbar. Das ist kein Luxus - in
|
||||
* einer Betreuungsakte ist ein versehentlich geloeschter Bescheid ein
|
||||
* echtes Problem, und der Nachweis gegenueber dem Gericht haengt daran.
|
||||
*
|
||||
* Endgueltig geloescht wird nur hier, nur einzeln und nur nach frischer
|
||||
* Anmeldung. Danach ist der Datensatz weg; im Audit-Log bleibt stehen, dass
|
||||
* es ihn gab und wer ihn entfernt hat.
|
||||
*/
|
||||
|
||||
export type Aktionszustand = {
|
||||
fehler?: string
|
||||
erfolg?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Was im Papierkorb auftauchen kann. Bewusst eine feste Liste und keine
|
||||
* dynamische Modellsuche: Der Entitaetsname kommt aus einem Formular, und
|
||||
* daraus darf niemals ein beliebiger Datenbankzugriff werden.
|
||||
*/
|
||||
type Eintragsart = {
|
||||
bezeichnung: string
|
||||
mehrzahl: string
|
||||
modul: Modul
|
||||
laden: (betreuteId: string, seit: Date) => Promise<PapierkorbEintrag[]>
|
||||
wiederherstellen: (id: string, betreuteId: string) => Promise<void>
|
||||
endgueltigLoeschen: (id: string, betreuteId: string) => Promise<void>
|
||||
}
|
||||
|
||||
export type PapierkorbEintrag = {
|
||||
id: string
|
||||
entitaet: string
|
||||
bezeichnung: string
|
||||
geloeschtAm: Date
|
||||
}
|
||||
|
||||
/** Kleine Hilfe, damit die Tabelle unten nicht 20-mal dasselbe wiederholt. */
|
||||
function einfach<T extends { id: string; geloeschtAm: Date | null }>(
|
||||
entitaet: string,
|
||||
bezeichnung: string,
|
||||
mehrzahl: string,
|
||||
modul: Modul,
|
||||
zugriff: {
|
||||
findMany: (args: unknown) => Promise<T[]>
|
||||
update: (args: unknown) => Promise<unknown>
|
||||
delete: (args: unknown) => Promise<unknown>
|
||||
},
|
||||
titelFeld: string,
|
||||
): Eintragsart {
|
||||
return {
|
||||
bezeichnung,
|
||||
mehrzahl,
|
||||
modul,
|
||||
laden: async (betreuteId, seit) => {
|
||||
const treffer = await zugriff.findMany({
|
||||
where: { betreuteId, geloeschtAm: { gte: seit } },
|
||||
orderBy: { geloeschtAm: 'desc' },
|
||||
})
|
||||
return treffer.map((datensatz) => ({
|
||||
id: datensatz.id,
|
||||
entitaet,
|
||||
bezeichnung:
|
||||
String((datensatz as unknown as Record<string, unknown>)[titelFeld] ?? '(ohne Titel)'),
|
||||
geloeschtAm: datensatz.geloeschtAm!,
|
||||
}))
|
||||
},
|
||||
wiederherstellen: async (id, betreuteId) => {
|
||||
await zugriff.update({ where: { id, betreuteId }, data: { geloeschtAm: null } })
|
||||
},
|
||||
endgueltigLoeschen: async (id, betreuteId) => {
|
||||
await zugriff.delete({ where: { id, betreuteId } })
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function eintragsarten(): Record<string, Eintragsart> {
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
const z = db as any
|
||||
return {
|
||||
Vorgang: einfach('Vorgang', 'Vorgang', 'Vorgänge', 'VORGAENGE', z.vorgang, 'titel'),
|
||||
Frist: einfach('Frist', 'Frist', 'Fristen', 'FRISTEN', z.frist, 'titel'),
|
||||
Dokument: einfach('Dokument', 'Dokument', 'Dokumente', 'DOKUMENTE', z.dokument, 'titel'),
|
||||
Konto: einfach('Konto', 'Konto', 'Konten', 'FINANZEN', z.konto, 'bezeichnung'),
|
||||
Buchung: einfach('Buchung', 'Buchung', 'Buchungen', 'FINANZEN', z.buchung, 'gegenstelle'),
|
||||
Vermoegensposten: einfach(
|
||||
'Vermoegensposten',
|
||||
'Vermögensposten',
|
||||
'Vermögensposten',
|
||||
'FINANZEN',
|
||||
z.vermoegensposten,
|
||||
'bezeichnung',
|
||||
),
|
||||
Immobilie: einfach('Immobilie', 'Immobilie', 'Immobilien', 'IMMOBILIE', z.immobilie, 'bezeichnung'),
|
||||
Vertrag: einfach('Vertrag', 'Vertrag', 'Verträge', 'VERTRAEGE', z.vertrag, 'bezeichnung'),
|
||||
Kontakt: einfach('Kontakt', 'Kontakt', 'Kontakte', 'KONTAKTE', z.kontakt, 'name'),
|
||||
Kontaktprotokoll: einfach(
|
||||
'Kontaktprotokoll',
|
||||
'Kontaktprotokoll',
|
||||
'Kontaktprotokolle',
|
||||
'KONTAKTE',
|
||||
z.kontaktprotokoll,
|
||||
'gespraechspartner',
|
||||
),
|
||||
Gesundheitsereignis: einfach(
|
||||
'Gesundheitsereignis',
|
||||
'Gesundheitsereignis',
|
||||
'Gesundheitsereignisse',
|
||||
'GESUNDHEIT',
|
||||
z.gesundheitsereignis,
|
||||
'titel',
|
||||
),
|
||||
Genehmigung: einfach(
|
||||
'Genehmigung',
|
||||
'Genehmigung',
|
||||
'Genehmigungen',
|
||||
'VORGAENGE',
|
||||
z.genehmigung,
|
||||
'titel',
|
||||
),
|
||||
SozialhilfeAntrag: einfach(
|
||||
'SozialhilfeAntrag',
|
||||
'Sozialhilfeantrag',
|
||||
'Sozialhilfeanträge',
|
||||
'SOZIALHILFE',
|
||||
z.sozialhilfeAntrag,
|
||||
'leistungsart',
|
||||
),
|
||||
Leistung: einfach('Leistung', 'Leistung', 'Leistungen', 'SOZIALHILFE', z.leistung, 'bezeichnung'),
|
||||
Bericht: einfach('Bericht', 'Bericht', 'Berichte', 'BERICHTE', z.bericht, 'titel'),
|
||||
}
|
||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||
}
|
||||
|
||||
/** Alles, was in den letzten 30 Tagen geloescht wurde. */
|
||||
export async function papierkorbLaden(betreuteId: string): Promise<
|
||||
{ art: string; bezeichnung: string; eintraege: PapierkorbEintrag[] }[]
|
||||
> {
|
||||
const { zugang } = await erfordereZugang(betreuteId, 'STAMMDATEN', 'lesen')
|
||||
const seit = papierkorbGrenze()
|
||||
|
||||
const gruppen: { art: string; bezeichnung: string; eintraege: PapierkorbEintrag[] }[] = []
|
||||
for (const [art, beschreibung] of Object.entries(eintragsarten())) {
|
||||
// Ein Mitleser sieht auch im Papierkorb nur, was ihm freigegeben ist.
|
||||
if (zugang.rolle === 'MITLESER' && !zugang.freigegebeneModule.includes(beschreibung.modul)) {
|
||||
continue
|
||||
}
|
||||
const eintraege = await beschreibung.laden(betreuteId, seit)
|
||||
if (eintraege.length > 0) {
|
||||
gruppen.push({ art, bezeichnung: beschreibung.mehrzahl, eintraege })
|
||||
}
|
||||
}
|
||||
return gruppen
|
||||
}
|
||||
|
||||
export async function wiederherstellen(
|
||||
_zustand: Aktionszustand,
|
||||
formular: FormData,
|
||||
): Promise<Aktionszustand> {
|
||||
const gelesen = papierkorbSchema.safeParse({
|
||||
betreuteId: formular.get('betreuteId'),
|
||||
entitaet: formular.get('entitaet'),
|
||||
entitaetId: formular.get('entitaetId'),
|
||||
})
|
||||
if (!gelesen.success) return { fehler: 'Diese Angaben ergeben keinen Sinn.' }
|
||||
|
||||
const art = eintragsarten()[gelesen.data.entitaet]
|
||||
if (!art) return { fehler: 'Diese Art von Datensatz kennt der Papierkorb nicht.' }
|
||||
|
||||
const { benutzer, zugang } = await erfordereZugang(gelesen.data.betreuteId, art.modul, 'schreiben')
|
||||
if (zugang.rolle === 'MITLESER') return { fehler: 'Als Mitleser darfst du nichts wiederherstellen.' }
|
||||
|
||||
await art.wiederherstellen(gelesen.data.entitaetId, gelesen.data.betreuteId)
|
||||
|
||||
await protokollieren({
|
||||
aktion: 'WIEDERHERGESTELLT',
|
||||
entitaet: gelesen.data.entitaet,
|
||||
entitaetId: gelesen.data.entitaetId,
|
||||
bezeichnung: `${art.bezeichnung} aus dem Papierkorb geholt`,
|
||||
betreuteId: gelesen.data.betreuteId,
|
||||
userId: benutzer.id,
|
||||
userEmail: benutzer.email,
|
||||
userName: benutzer.name,
|
||||
})
|
||||
|
||||
revalidatePath('/einstellungen/papierkorb')
|
||||
return { erfolg: `${art.bezeichnung} ist wieder da.` }
|
||||
}
|
||||
|
||||
/**
|
||||
* Endgueltig loeschen. Danach ist der Datensatz weg - auch die Datei im
|
||||
* Objektspeicher, falls es eine gibt. Nur nach frischer Anmeldung.
|
||||
*/
|
||||
export async function endgueltigLoeschen(
|
||||
_zustand: Aktionszustand,
|
||||
formular: FormData,
|
||||
): Promise<Aktionszustand> {
|
||||
const gelesen = papierkorbSchema.safeParse({
|
||||
betreuteId: formular.get('betreuteId'),
|
||||
entitaet: formular.get('entitaet'),
|
||||
entitaetId: formular.get('entitaetId'),
|
||||
})
|
||||
if (!gelesen.success) return { fehler: 'Diese Angaben ergeben keinen Sinn.' }
|
||||
|
||||
const art = eintragsarten()[gelesen.data.entitaet]
|
||||
if (!art) return { fehler: 'Diese Art von Datensatz kennt der Papierkorb nicht.' }
|
||||
|
||||
const { benutzer, zugang } = await erfordereZugang(gelesen.data.betreuteId, art.modul, 'schreiben')
|
||||
if (zugang.rolle === 'MITLESER') return { fehler: 'Als Mitleser darfst du nichts löschen.' }
|
||||
await erfordereFrischeAnmeldung(benutzer, '/einstellungen/papierkorb')
|
||||
|
||||
// Bei Dokumenten und Berichten haengt eine Datei daran. Sie wird zuerst
|
||||
// entfernt: Bliebe sie liegen, waere sie eine verwaiste Kopie von Daten,
|
||||
// die ausdruecklich verschwinden sollten.
|
||||
if (gelesen.data.entitaet === 'Dokument' || gelesen.data.entitaet === 'Bericht') {
|
||||
const tabelle = gelesen.data.entitaet === 'Dokument' ? db.dokument : db.bericht
|
||||
const datensatz = await tabelle.findFirst({
|
||||
where: { id: gelesen.data.entitaetId, betreuteId: gelesen.data.betreuteId },
|
||||
select: { objektSchluessel: true },
|
||||
})
|
||||
if (datensatz?.objektSchluessel) {
|
||||
try {
|
||||
await entfernen(datensatz.objektSchluessel)
|
||||
} catch (fehler) {
|
||||
console.error('[papierkorb] Datei konnte nicht entfernt werden:', fehler)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await art.endgueltigLoeschen(gelesen.data.entitaetId, gelesen.data.betreuteId)
|
||||
|
||||
await protokollieren({
|
||||
aktion: 'ENDGUELTIG_GELOESCHT',
|
||||
entitaet: gelesen.data.entitaet,
|
||||
entitaetId: gelesen.data.entitaetId,
|
||||
bezeichnung: `${art.bezeichnung} endgültig gelöscht`,
|
||||
betreuteId: gelesen.data.betreuteId,
|
||||
userId: benutzer.id,
|
||||
userEmail: benutzer.email,
|
||||
userName: benutzer.name,
|
||||
})
|
||||
|
||||
revalidatePath('/einstellungen/papierkorb')
|
||||
return { erfolg: `${art.bezeichnung} ist endgültig entfernt.` }
|
||||
}
|
||||
'use server'
|
||||
|
||||
import { revalidatePath } from 'next/cache'
|
||||
import type { Modul } from '@prisma/client'
|
||||
import { db, papierkorbGrenze } from '@/lib/db'
|
||||
import { protokollieren } from '@/lib/audit'
|
||||
import { erfordereFrischeAnmeldung, erfordereZugang } from '@/lib/auth/sitzung'
|
||||
import { entfernen } from '@/lib/speicher'
|
||||
import { papierkorbSchema } from '@/lib/validierung/freigaben'
|
||||
|
||||
/**
|
||||
* Papierkorb.
|
||||
*
|
||||
* Loeschen heisst in dieser Anwendung: "geloeschtAm" setzen. Der Datensatz
|
||||
* bleibt 30 Tage sichtbar und wiederherstellbar. Das ist kein Luxus - in
|
||||
* einer Betreuungsakte ist ein versehentlich geloeschter Bescheid ein
|
||||
* echtes Problem, und der Nachweis gegenueber dem Gericht haengt daran.
|
||||
*
|
||||
* Endgueltig geloescht wird nur hier, nur einzeln und nur nach frischer
|
||||
* Anmeldung. Danach ist der Datensatz weg; im Audit-Log bleibt stehen, dass
|
||||
* es ihn gab und wer ihn entfernt hat.
|
||||
*/
|
||||
|
||||
export type Aktionszustand = {
|
||||
fehler?: string
|
||||
erfolg?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Was im Papierkorb auftauchen kann. Bewusst eine feste Liste und keine
|
||||
* dynamische Modellsuche: Der Entitaetsname kommt aus einem Formular, und
|
||||
* daraus darf niemals ein beliebiger Datenbankzugriff werden.
|
||||
*/
|
||||
type Eintragsart = {
|
||||
bezeichnung: string
|
||||
mehrzahl: string
|
||||
modul: Modul
|
||||
laden: (betreuteId: string, seit: Date) => Promise<PapierkorbEintrag[]>
|
||||
wiederherstellen: (id: string, betreuteId: string) => Promise<void>
|
||||
endgueltigLoeschen: (id: string, betreuteId: string) => Promise<void>
|
||||
}
|
||||
|
||||
export type PapierkorbEintrag = {
|
||||
id: string
|
||||
entitaet: string
|
||||
bezeichnung: string
|
||||
geloeschtAm: Date
|
||||
}
|
||||
|
||||
/** Kleine Hilfe, damit die Tabelle unten nicht 20-mal dasselbe wiederholt. */
|
||||
function einfach<T extends { id: string; geloeschtAm: Date | null }>(
|
||||
entitaet: string,
|
||||
bezeichnung: string,
|
||||
mehrzahl: string,
|
||||
modul: Modul,
|
||||
zugriff: {
|
||||
findMany: (args: unknown) => Promise<T[]>
|
||||
update: (args: unknown) => Promise<unknown>
|
||||
delete: (args: unknown) => Promise<unknown>
|
||||
},
|
||||
titelFeld: string,
|
||||
): Eintragsart {
|
||||
return {
|
||||
bezeichnung,
|
||||
mehrzahl,
|
||||
modul,
|
||||
laden: async (betreuteId, seit) => {
|
||||
const treffer = await zugriff.findMany({
|
||||
where: { betreuteId, geloeschtAm: { gte: seit } },
|
||||
orderBy: { geloeschtAm: 'desc' },
|
||||
})
|
||||
return treffer.map((datensatz) => ({
|
||||
id: datensatz.id,
|
||||
entitaet,
|
||||
bezeichnung:
|
||||
String((datensatz as unknown as Record<string, unknown>)[titelFeld] ?? '(ohne Titel)'),
|
||||
geloeschtAm: datensatz.geloeschtAm!,
|
||||
}))
|
||||
},
|
||||
wiederherstellen: async (id, betreuteId) => {
|
||||
await zugriff.update({ where: { id, betreuteId }, data: { geloeschtAm: null } })
|
||||
},
|
||||
endgueltigLoeschen: async (id, betreuteId) => {
|
||||
await zugriff.delete({ where: { id, betreuteId } })
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function eintragsarten(): Record<string, Eintragsart> {
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
const z = db as any
|
||||
return {
|
||||
Vorgang: einfach('Vorgang', 'Vorgang', 'Vorgänge', 'VORGAENGE', z.vorgang, 'titel'),
|
||||
Frist: einfach('Frist', 'Frist', 'Fristen', 'FRISTEN', z.frist, 'titel'),
|
||||
Dokument: einfach('Dokument', 'Dokument', 'Dokumente', 'DOKUMENTE', z.dokument, 'titel'),
|
||||
Konto: einfach('Konto', 'Konto', 'Konten', 'FINANZEN', z.konto, 'bezeichnung'),
|
||||
Buchung: einfach('Buchung', 'Buchung', 'Buchungen', 'FINANZEN', z.buchung, 'gegenstelle'),
|
||||
Vermoegensposten: einfach(
|
||||
'Vermoegensposten',
|
||||
'Vermögensposten',
|
||||
'Vermögensposten',
|
||||
'FINANZEN',
|
||||
z.vermoegensposten,
|
||||
'bezeichnung',
|
||||
),
|
||||
Immobilie: einfach('Immobilie', 'Immobilie', 'Immobilien', 'IMMOBILIE', z.immobilie, 'bezeichnung'),
|
||||
Vertrag: einfach('Vertrag', 'Vertrag', 'Verträge', 'VERTRAEGE', z.vertrag, 'bezeichnung'),
|
||||
Kontakt: einfach('Kontakt', 'Kontakt', 'Kontakte', 'KONTAKTE', z.kontakt, 'name'),
|
||||
Kontaktprotokoll: einfach(
|
||||
'Kontaktprotokoll',
|
||||
'Kontaktprotokoll',
|
||||
'Kontaktprotokolle',
|
||||
'KONTAKTE',
|
||||
z.kontaktprotokoll,
|
||||
'gespraechspartner',
|
||||
),
|
||||
Gesundheitsereignis: einfach(
|
||||
'Gesundheitsereignis',
|
||||
'Gesundheitsereignis',
|
||||
'Gesundheitsereignisse',
|
||||
'GESUNDHEIT',
|
||||
z.gesundheitsereignis,
|
||||
'titel',
|
||||
),
|
||||
Genehmigung: einfach(
|
||||
'Genehmigung',
|
||||
'Genehmigung',
|
||||
'Genehmigungen',
|
||||
'VORGAENGE',
|
||||
z.genehmigung,
|
||||
'titel',
|
||||
),
|
||||
SozialhilfeAntrag: einfach(
|
||||
'SozialhilfeAntrag',
|
||||
'Sozialhilfeantrag',
|
||||
'Sozialhilfeanträge',
|
||||
'SOZIALHILFE',
|
||||
z.sozialhilfeAntrag,
|
||||
'leistungsart',
|
||||
),
|
||||
Leistung: einfach('Leistung', 'Leistung', 'Leistungen', 'SOZIALHILFE', z.leistung, 'bezeichnung'),
|
||||
Bericht: einfach('Bericht', 'Bericht', 'Berichte', 'BERICHTE', z.bericht, 'titel'),
|
||||
}
|
||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||
}
|
||||
|
||||
/** Alles, was in den letzten 30 Tagen geloescht wurde. */
|
||||
export async function papierkorbLaden(betreuteId: string): Promise<
|
||||
{ art: string; bezeichnung: string; eintraege: PapierkorbEintrag[] }[]
|
||||
> {
|
||||
const { zugang } = await erfordereZugang(betreuteId, 'STAMMDATEN', 'lesen')
|
||||
const seit = papierkorbGrenze()
|
||||
|
||||
const gruppen: { art: string; bezeichnung: string; eintraege: PapierkorbEintrag[] }[] = []
|
||||
for (const [art, beschreibung] of Object.entries(eintragsarten())) {
|
||||
// Ein Mitleser sieht auch im Papierkorb nur, was ihm freigegeben ist.
|
||||
if (zugang.rolle === 'MITLESER' && !zugang.freigegebeneModule.includes(beschreibung.modul)) {
|
||||
continue
|
||||
}
|
||||
const eintraege = await beschreibung.laden(betreuteId, seit)
|
||||
if (eintraege.length > 0) {
|
||||
gruppen.push({ art, bezeichnung: beschreibung.mehrzahl, eintraege })
|
||||
}
|
||||
}
|
||||
return gruppen
|
||||
}
|
||||
|
||||
/**
|
||||
* Haengt an diesem Datensatz eine Datei im Objektspeicher?
|
||||
*
|
||||
* Die beiden Faelle werden ausdruecklich getrennt abgefragt statt ueber
|
||||
* eine gemeinsam gehaltene Tabellenvariable: Prisma gibt jedem Modell
|
||||
* eigene Signaturen, und eine Variable, die mal das eine und mal das
|
||||
* andere Delegate haelt, laesst sich nicht mehr aufrufen.
|
||||
*/
|
||||
async function objektSchluesselFinden(
|
||||
entitaet: string,
|
||||
entitaetId: string,
|
||||
betreuteId: string,
|
||||
): Promise<string | null> {
|
||||
if (entitaet === 'Dokument') {
|
||||
const dokument = await db.dokument.findFirst({
|
||||
where: { id: entitaetId, betreuteId },
|
||||
select: { objektSchluessel: true },
|
||||
})
|
||||
return dokument?.objektSchluessel ?? null
|
||||
}
|
||||
if (entitaet === 'Bericht') {
|
||||
const bericht = await db.bericht.findFirst({
|
||||
where: { id: entitaetId, betreuteId },
|
||||
select: { objektSchluessel: true },
|
||||
})
|
||||
return bericht?.objektSchluessel ?? null
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export async function wiederherstellen(
|
||||
_zustand: Aktionszustand,
|
||||
formular: FormData,
|
||||
): Promise<Aktionszustand> {
|
||||
const gelesen = papierkorbSchema.safeParse({
|
||||
betreuteId: formular.get('betreuteId'),
|
||||
entitaet: formular.get('entitaet'),
|
||||
entitaetId: formular.get('entitaetId'),
|
||||
})
|
||||
if (!gelesen.success) return { fehler: 'Diese Angaben ergeben keinen Sinn.' }
|
||||
|
||||
const art = eintragsarten()[gelesen.data.entitaet]
|
||||
if (!art) return { fehler: 'Diese Art von Datensatz kennt der Papierkorb nicht.' }
|
||||
|
||||
const { benutzer, zugang } = await erfordereZugang(gelesen.data.betreuteId, art.modul, 'schreiben')
|
||||
if (zugang.rolle === 'MITLESER') return { fehler: 'Als Mitleser darfst du nichts wiederherstellen.' }
|
||||
|
||||
await art.wiederherstellen(gelesen.data.entitaetId, gelesen.data.betreuteId)
|
||||
|
||||
await protokollieren({
|
||||
aktion: 'WIEDERHERGESTELLT',
|
||||
entitaet: gelesen.data.entitaet,
|
||||
entitaetId: gelesen.data.entitaetId,
|
||||
bezeichnung: `${art.bezeichnung} aus dem Papierkorb geholt`,
|
||||
betreuteId: gelesen.data.betreuteId,
|
||||
userId: benutzer.id,
|
||||
userEmail: benutzer.email,
|
||||
userName: benutzer.name,
|
||||
})
|
||||
|
||||
revalidatePath('/einstellungen/papierkorb')
|
||||
return { erfolg: `${art.bezeichnung} ist wieder da.` }
|
||||
}
|
||||
|
||||
/**
|
||||
* Endgueltig loeschen. Danach ist der Datensatz weg - auch die Datei im
|
||||
* Objektspeicher, falls es eine gibt. Nur nach frischer Anmeldung.
|
||||
*/
|
||||
export async function endgueltigLoeschen(
|
||||
_zustand: Aktionszustand,
|
||||
formular: FormData,
|
||||
): Promise<Aktionszustand> {
|
||||
const gelesen = papierkorbSchema.safeParse({
|
||||
betreuteId: formular.get('betreuteId'),
|
||||
entitaet: formular.get('entitaet'),
|
||||
entitaetId: formular.get('entitaetId'),
|
||||
})
|
||||
if (!gelesen.success) return { fehler: 'Diese Angaben ergeben keinen Sinn.' }
|
||||
|
||||
const art = eintragsarten()[gelesen.data.entitaet]
|
||||
if (!art) return { fehler: 'Diese Art von Datensatz kennt der Papierkorb nicht.' }
|
||||
|
||||
const { benutzer, zugang } = await erfordereZugang(gelesen.data.betreuteId, art.modul, 'schreiben')
|
||||
if (zugang.rolle === 'MITLESER') return { fehler: 'Als Mitleser darfst du nichts löschen.' }
|
||||
await erfordereFrischeAnmeldung(benutzer, '/einstellungen/papierkorb')
|
||||
|
||||
// Bei Dokumenten und Berichten haengt eine Datei daran. Sie wird zuerst
|
||||
// entfernt: Bliebe sie liegen, waere sie eine verwaiste Kopie von Daten,
|
||||
// die ausdruecklich verschwinden sollten.
|
||||
const schluessel = await objektSchluesselFinden(
|
||||
gelesen.data.entitaet,
|
||||
gelesen.data.entitaetId,
|
||||
gelesen.data.betreuteId,
|
||||
)
|
||||
if (schluessel) {
|
||||
try {
|
||||
await entfernen(schluessel)
|
||||
} catch (fehler) {
|
||||
console.error('[papierkorb] Datei konnte nicht entfernt werden:', fehler)
|
||||
}
|
||||
}
|
||||
|
||||
await art.endgueltigLoeschen(gelesen.data.entitaetId, gelesen.data.betreuteId)
|
||||
|
||||
await protokollieren({
|
||||
aktion: 'ENDGUELTIG_GELOESCHT',
|
||||
entitaet: gelesen.data.entitaet,
|
||||
entitaetId: gelesen.data.entitaetId,
|
||||
bezeichnung: `${art.bezeichnung} endgültig gelöscht`,
|
||||
betreuteId: gelesen.data.betreuteId,
|
||||
userId: benutzer.id,
|
||||
userEmail: benutzer.email,
|
||||
userName: benutzer.name,
|
||||
})
|
||||
|
||||
revalidatePath('/einstellungen/papierkorb')
|
||||
return { erfolg: `${art.bezeichnung} ist endgültig entfernt.` }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user