Drei Fehler, die sich beim Durchsehen der Typpruefung und der
Browserkonsole gezeigt haben.
1. oeffnen() lieferte Record<string, string | null>. Zusammen mit
noUncheckedIndexedAccess wurde daraus bei jedem Zugriff
string | null | undefined - und jede aufrufende Seite haette das
wegkaesen muessen, obwohl die Felder immer da sind. Jetzt leitet ein
Mapped Type die Klartextnamen aus VERSCHLUESSELTE_FELDER ab:
oeffnen('Betreute', zeile).diagnosen ist sauber string | null.
Das hat auf einen Schlag 17 Folgefehler in fremden Modulen erledigt.
2. Die CSP blockierte im Entwicklungsmodus die eigenen Skripte.
'strict-dynamic' schaltet die Freigabe nach Herkunft ab, also auch
'self' - und Hot-Reload und Fehleranzeige laden ohne Nonce. Man sah
eine tote Seite statt der Fehlermeldung. Im Produktivbetrieb bleibt
es bei Nonce plus 'strict-dynamic', im Entwicklungsmodus gilt die
lockere Fassung.
3. Das Theme-Skript im Layout meldete bei jedem Seitenaufruf eine
Hydrations-Abweichung: React gibt das nonce-Attribut absichtlich
nicht an den Browser weiter, serverseitig steht es aber im HTML.
suppressHydrationWarning am Skript selbst - das html-Element allein
deckt das nicht ab.
Nebenbei: Algorithm.Argon2id ist ein const enum und laesst sich mit
isolatedModules nicht importieren. Die Angabe entfaellt, @node-rs/argon2
benutzt von sich aus Argon2id.
Die Anmeldeseite laedt jetzt ohne einen einzigen Konsolenfehler.
Etappe: 8
Phase: Haertung
547 lines
22 KiB
TypeScript
547 lines
22 KiB
TypeScript
/**
|
|
* Startdaten.
|
|
*
|
|
* npm run db:seed
|
|
*
|
|
* Der Seed legt bewusst KEINE echten Personendaten an. Er richtet nur den
|
|
* ersten Zugang ein; alles Weitere fragt der Ersteinrichtungs-Assistent
|
|
* beim ersten Start ab. So landen keine Gesundheitsdaten im Repository,
|
|
* und der Seed bleibt auch dann brauchbar, wenn eine zweite Betreuung
|
|
* dazukommt.
|
|
*
|
|
* Zwei Schalter fuer die Entwicklung:
|
|
*
|
|
* SEED_DEMO=true legt einen vollstaendigen Beispielfall an
|
|
* (Erika Musterfrau). Nur ausserhalb der
|
|
* Produktion, und in der Oberflaeche als
|
|
* Demo gekennzeichnet.
|
|
* SEED_DEV_TOTP_SECRET=... aktiviert den zweiten Faktor mit einem
|
|
* bekannten Geheimnis, damit automatisierte
|
|
* Tests sich anmelden koennen. Wird in der
|
|
* Produktion ignoriert.
|
|
*
|
|
* Der Seed ist idempotent: mehrfaches Ausfuehren aendert nichts.
|
|
*/
|
|
import { PrismaClient } from '@prisma/client'
|
|
import { hash } from '@node-rs/argon2'
|
|
import { createCipheriv, randomBytes } from 'node:crypto'
|
|
|
|
const db = new PrismaClient()
|
|
|
|
// Muss zu ARGON2_OPTIONEN in src/lib/crypto/hash.ts passen.
|
|
// Ohne Angabe benutzt @node-rs/argon2 von sich aus Argon2id.
|
|
const ARGON2 = {
|
|
memoryCost: 19_456,
|
|
timeCost: 2,
|
|
parallelism: 1,
|
|
} as const
|
|
|
|
const istProduktion = process.env.NODE_ENV === 'production'
|
|
|
|
/**
|
|
* Verschluesselt wie src/lib/crypto/feld.ts. Bewusst hier noch einmal
|
|
* ausgeschrieben: der Seed laeuft ausserhalb von Next.js und kann das
|
|
* Modul mit seinem "server-only"-Import nicht laden.
|
|
*/
|
|
function verschluesseln(klartext: string | null, kontext: string): string | null {
|
|
if (!klartext) return null
|
|
|
|
const version = Number.parseInt(process.env.ENC_KEY_AKTIV ?? '1', 10)
|
|
const roh = process.env[`ENC_KEY_V${version}`]
|
|
if (!roh) throw new Error(`ENC_KEY_V${version} fehlt. Erzeuge ihn mit "npm run schluessel".`)
|
|
|
|
const schluessel = Buffer.from(roh, 'base64')
|
|
if (schluessel.length !== 32) throw new Error(`ENC_KEY_V${version} muss 32 Bytes lang sein.`)
|
|
|
|
const iv = randomBytes(12)
|
|
const cipher = createCipheriv('aes-256-gcm', schluessel, iv, { authTagLength: 16 })
|
|
cipher.setAAD(Buffer.from(`bc1:${version}:${kontext}`, 'utf8'))
|
|
const geheim = Buffer.concat([cipher.update(klartext, 'utf8'), cipher.final()])
|
|
|
|
return [
|
|
'bc1',
|
|
version,
|
|
iv.toString('base64url'),
|
|
cipher.getAuthTag().toString('base64url'),
|
|
geheim.toString('base64url'),
|
|
].join('.')
|
|
}
|
|
|
|
function datum(text: string): Date {
|
|
return new Date(`${text}T00:00:00.000Z`)
|
|
}
|
|
|
|
function cent(euro: number): bigint {
|
|
return BigInt(Math.round(euro * 100))
|
|
}
|
|
|
|
async function ersterZugang() {
|
|
const email = (process.env.SEED_ADMIN_EMAIL ?? '').trim().toLowerCase()
|
|
const passwort = process.env.SEED_ADMIN_PASSWORT
|
|
|
|
if (!email || !passwort) {
|
|
console.log(
|
|
'Kein SEED_ADMIN_EMAIL / SEED_ADMIN_PASSWORT gesetzt - der erste Zugang wird beim ' +
|
|
'Aufruf von /ersteinrichtung im Browser angelegt.',
|
|
)
|
|
return null
|
|
}
|
|
|
|
const vorhanden = await db.user.findUnique({ where: { email } })
|
|
if (vorhanden) {
|
|
console.log(`Zugang ${email} besteht bereits.`)
|
|
return vorhanden
|
|
}
|
|
|
|
// Der zweite Faktor bleibt normalerweise offen und wird beim ersten
|
|
// Anmelden eingerichtet - nur die Entwicklung darf ihn vorbelegen.
|
|
const devSecret = !istProduktion ? process.env.SEED_DEV_TOTP_SECRET : undefined
|
|
|
|
const benutzer = await db.user.create({
|
|
data: {
|
|
email,
|
|
name: process.env.SEED_ADMIN_NAME ?? 'Betreuer',
|
|
passwortHash: await hash(passwort, ARGON2),
|
|
rolle: 'ADMIN',
|
|
...(devSecret
|
|
? {
|
|
totpSecretVerschluesselt: verschluesseln(devSecret, 'totpSecretVerschluesselt'),
|
|
totpAktiviertAm: new Date(),
|
|
}
|
|
: {}),
|
|
},
|
|
})
|
|
|
|
console.log(`Zugang ${email} angelegt.`)
|
|
if (devSecret) {
|
|
console.log(' Zweiter Faktor mit dem Entwicklungs-Geheimnis vorbelegt.')
|
|
} else {
|
|
console.log(' Der zweite Faktor wird beim ersten Anmelden eingerichtet.')
|
|
}
|
|
|
|
return benutzer
|
|
}
|
|
|
|
/**
|
|
* Vollstaendiger Beispielfall. Nur mit SEED_DEMO=true und nie in der
|
|
* Produktion - er soll zeigen, wie die Anwendung mit Inhalt aussieht.
|
|
*/
|
|
async function demofall(userId: string) {
|
|
const vorhanden = await db.betreute.findFirst({
|
|
where: { nachname: 'Musterfrau', vorname: 'Erika' },
|
|
})
|
|
if (vorhanden) {
|
|
console.log('Demofall besteht bereits.')
|
|
return
|
|
}
|
|
|
|
const betreute = await db.betreute.create({
|
|
data: {
|
|
anrede: 'Frau',
|
|
vorname: 'Erika',
|
|
nachname: 'Musterfrau',
|
|
geburtsname: 'Beispiel',
|
|
geburtsdatum: datum('1943-04-17'),
|
|
geburtsort: 'Bayreuth',
|
|
|
|
heimName: 'Seniorenzentrum St. Katharina',
|
|
heimStrasse: 'Kirchweg 12',
|
|
heimPlz: '95444',
|
|
heimOrt: 'Bayreuth',
|
|
heimZimmer: '218',
|
|
heimTelefon: '0921 123456-218',
|
|
heimEinzugAm: datum('2026-01-19'),
|
|
|
|
fruehereStrasse: 'Am Sonnenhang 4',
|
|
frueherePlz: '95463',
|
|
fruehererOrt: 'Bindlach',
|
|
|
|
krankenkasse: 'AOK Bayern',
|
|
krankenversichertennummerVerschluesselt: verschluesseln(
|
|
'A123456789',
|
|
'krankenversichertennummerVerschluesselt',
|
|
),
|
|
pflegekasse: 'AOK Pflegekasse Bayern',
|
|
pflegegrad: 3,
|
|
pflegegradSeit: datum('2025-11-01'),
|
|
pflegegradBescheidVom: datum('2025-10-22'),
|
|
schwerbehinderungGdB: 80,
|
|
schwerbehinderungMerkzeichen: 'G, B',
|
|
diagnosenVerschluesselt: verschluesseln(
|
|
[
|
|
'COPD GOLD III mit chronischer respiratorischer Insuffizienz (J44.09)',
|
|
'Langzeitsauerstofftherapie seit 03/2025, 2 l/min, 16 h/Tag',
|
|
'Arterielle Hypertonie (I10)',
|
|
'Osteoporose mit Wirbelkörperfraktur BWK 12 (M80.08)',
|
|
'Beginnende vaskuläre Demenz (F01.9)',
|
|
].join('\n'),
|
|
'diagnosenVerschluesselt',
|
|
),
|
|
ermittelterWilleVerschluesselt: verschluesseln(
|
|
'Möchte im Seniorenzentrum bleiben, dort ist eine frühere Nachbarin. ' +
|
|
'Will das Haus in Bindlach "nicht einfach hergeben", versteht aber, dass die ' +
|
|
'Pflegekosten gedeckt werden müssen. Wünscht keine künstliche Beatmung und keine ' +
|
|
'Verlegung in eine Klinik, wenn es zum Ende geht — so auch in der Patientenverfügung ' +
|
|
'vom 08.03.2019 festgehalten. Besuche bitte nachmittags, vormittags ist sie erschöpft.',
|
|
'ermittelterWilleVerschluesselt',
|
|
),
|
|
patientenverfuegungVorhanden: true,
|
|
vorsorgevollmachtVorhanden: false,
|
|
|
|
betreuungsgericht: 'Amtsgericht Bayreuth',
|
|
aktenzeichenGericht: 'XVII 412/25',
|
|
bestellungAm: datum('2026-02-14'),
|
|
aufgabenkreise: [
|
|
'VERMOEGENSSORGE',
|
|
'GESUNDHEITSSORGE',
|
|
'AUFENTHALTSBESTIMMUNG',
|
|
'WOHNUNGSANGELEGENHEITEN',
|
|
'BEHOERDENANGELEGENHEITEN',
|
|
],
|
|
befreiterBetreuer: true,
|
|
ehrenamtlich: true,
|
|
|
|
aktenzeichenBezirk: 'HzP 2026/04711',
|
|
aktenzeichenSozialamt: 'SA-4711-26',
|
|
rentenversicherungsnummerVerschluesselt: verschluesseln(
|
|
'17 170443 M 512',
|
|
'rentenversicherungsnummerVerschluesselt',
|
|
),
|
|
notizenVerschluesselt: verschluesseln(
|
|
'Dies ist ein Demofall zum Ausprobieren. Er lässt sich unter ' +
|
|
'Einstellungen jederzeit vollständig löschen.',
|
|
'notizenVerschluesselt',
|
|
),
|
|
|
|
zugaenge: { create: { userId, rolle: 'BETREUER' } },
|
|
},
|
|
})
|
|
|
|
// --- Kontakte -----------------------------------------------------------
|
|
await db.kontakt.createMany({
|
|
data: [
|
|
{
|
|
betreuteId: betreute.id,
|
|
name: 'Frau Hartmann',
|
|
organisation: 'Amtsgericht Bayreuth',
|
|
rolle: 'RECHTSPFLEGER',
|
|
telefon: '0921 504-0',
|
|
aktenzeichen: 'XVII 412/25',
|
|
},
|
|
{
|
|
betreuteId: betreute.id,
|
|
name: 'Herr Doblinger',
|
|
organisation: 'Bezirk Oberfranken, Sozialverwaltung',
|
|
rolle: 'SACHBEARBEITER_BEZIRK',
|
|
telefon: '0921 604-1234',
|
|
email: 'sozialverwaltung@bezirk-oberfranken.de',
|
|
aktenzeichen: 'HzP 2026/04711',
|
|
},
|
|
{
|
|
betreuteId: betreute.id,
|
|
name: 'Frau Kellner',
|
|
organisation: 'Seniorenzentrum St. Katharina',
|
|
rolle: 'SOZIALDIENST_HEIM',
|
|
telefon: '0921 123456-30',
|
|
},
|
|
{
|
|
betreuteId: betreute.id,
|
|
name: 'Dr. med. Reuther',
|
|
organisation: 'Hausarztpraxis Bindlach',
|
|
rolle: 'HAUSARZT',
|
|
telefon: '09208 4477',
|
|
},
|
|
],
|
|
})
|
|
|
|
// --- Konten und Vermoegen ----------------------------------------------
|
|
const giro = await db.konto.create({
|
|
data: {
|
|
betreuteId: betreute.id,
|
|
bezeichnung: 'Girokonto',
|
|
bank: 'Sparkasse Bayreuth',
|
|
art: 'GIRO',
|
|
ibanVerschluesselt: verschluesseln('DE02773501100123456789', 'ibanVerschluesselt'),
|
|
saldostaende: {
|
|
create: [
|
|
{ stichtag: datum('2026-02-14'), betragCent: cent(4218.36), quelle: 'Kontoauszug 02/2026' },
|
|
{ stichtag: datum('2026-07-31'), betragCent: cent(2967.11), quelle: 'Kontoauszug 07/2026' },
|
|
],
|
|
},
|
|
},
|
|
})
|
|
|
|
await db.konto.create({
|
|
data: {
|
|
betreuteId: betreute.id,
|
|
bezeichnung: 'Verwahrgeld im Heim',
|
|
bank: 'Seniorenzentrum St. Katharina',
|
|
art: 'VERWAHRGELD_HEIM',
|
|
saldostaende: {
|
|
create: { stichtag: datum('2026-07-31'), betragCent: cent(184.2), quelle: 'Abrechnung Heim' },
|
|
},
|
|
},
|
|
})
|
|
|
|
const immobilie = await db.immobilie.create({
|
|
data: {
|
|
betreuteId: betreute.id,
|
|
bezeichnung: 'Einfamilienhaus Bindlach',
|
|
strasse: 'Am Sonnenhang 4',
|
|
plz: '95463',
|
|
ort: 'Bindlach',
|
|
grundbuchamt: 'Amtsgericht Bayreuth',
|
|
grundbuchBlatt: '3241',
|
|
gemarkung: 'Bindlach',
|
|
flurstueck: '812/3',
|
|
eigentumsanteil: '1/1',
|
|
wohnflaecheQm: 128,
|
|
grundstueckQm: 640,
|
|
baujahr: 1972,
|
|
bewohntStatus: 'leerstehend',
|
|
leerstandSeit: datum('2026-01-19'),
|
|
statusSozialhilfe: 'UNGEKLAERT',
|
|
statusBegruendungVerschluesselt: verschluesseln(
|
|
'Nicht mehr selbst bewohnt, seit dem Heimeinzug leerstehend. Schutz nach ' +
|
|
'§ 90 Abs. 2 Nr. 8 SGB XII greift daher voraussichtlich nicht. Zu prüfen: ' +
|
|
'Härtefall nach § 90 Abs. 3 SGB XII und Darlehen nach § 91 SGB XII statt ' +
|
|
'sofortiger Verwertung. Mit dem Bezirk noch nicht abschließend geklärt.',
|
|
'statusBegruendungVerschluesselt',
|
|
),
|
|
belastungen: {
|
|
create: {
|
|
art: 'Grundschuld',
|
|
abteilung: 'III',
|
|
laufendeNr: '2',
|
|
glaeubiger: 'Sparkasse Bayreuth',
|
|
betragCent: cent(30000),
|
|
bemerkung: 'Valutiert nach Auskunft der Bank nicht mehr, Löschungsbewilligung angefordert.',
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
const lebensversicherung = await db.vertrag.create({
|
|
data: {
|
|
betreuteId: betreute.id,
|
|
bezeichnung: 'Kapitallebensversicherung',
|
|
gesellschaft: 'Musterland Leben AG',
|
|
art: 'LEBENSVERSICHERUNG',
|
|
status: 'BEITRAGSFREI',
|
|
vertragsnummerVerschluesselt: verschluesseln('LV-8842119', 'vertragsnummerVerschluesselt'),
|
|
beginnAm: datum('1998-07-01'),
|
|
endeAm: datum('2028-07-01'),
|
|
bewertungSozialhilfe: 'STRITTIG',
|
|
bewertungBegruendungVerschluesselt: verschluesseln(
|
|
'Rückkaufswert liegt über dem Schonvermögen. Verwertung wäre grundsätzlich zumutbar, ' +
|
|
'aber der Vertrag läuft 2028 aus — Kündigung jetzt bedeutet spürbaren Verlust. ' +
|
|
'Härtefall nach § 90 Abs. 3 SGB XII geltend gemacht, Antwort des Bezirks steht aus.',
|
|
'bewertungBegruendungVerschluesselt',
|
|
),
|
|
rueckkaufswertAngefragtAm: datum('2026-03-02'),
|
|
},
|
|
})
|
|
|
|
await db.vertrag.create({
|
|
data: {
|
|
betreuteId: betreute.id,
|
|
bezeichnung: 'Sterbegeldversicherung',
|
|
gesellschaft: 'Nordheim Sterbekasse VVaG',
|
|
art: 'STERBEGELDVERSICHERUNG',
|
|
status: 'AKTIV',
|
|
vertragsnummerVerschluesselt: verschluesseln('SG-220417', 'vertragsnummerVerschluesselt'),
|
|
beitragCent: cent(18.4),
|
|
rhythmus: 'MONATLICH',
|
|
bewertungSozialhilfe: 'GESCHUETZT',
|
|
bewertungBegruendungVerschluesselt: verschluesseln(
|
|
'Angemessene Bestattungsvorsorge. Nach § 90 Abs. 3 SGB XII und ständiger ' +
|
|
'Rechtsprechung regelmäßig geschützt, weil sie der Würde der Bestattung dient.',
|
|
'bewertungBegruendungVerschluesselt',
|
|
),
|
|
},
|
|
})
|
|
|
|
await db.vermoegensposten.createMany({
|
|
data: [
|
|
{ betreuteId: betreute.id, bezeichnung: 'Girokonto Sparkasse', art: 'KONTO', kontoId: giro.id, bewertungSozialhilfe: 'VERWERTBAR' },
|
|
{ betreuteId: betreute.id, bezeichnung: 'Einfamilienhaus Bindlach', art: 'IMMOBILIE', immobilieId: immobilie.id, bewertungSozialhilfe: 'STRITTIG' },
|
|
{ betreuteId: betreute.id, bezeichnung: 'Kapitallebensversicherung', art: 'VERSICHERUNG', vertragId: lebensversicherung.id, bewertungSozialhilfe: 'STRITTIG' },
|
|
],
|
|
})
|
|
|
|
const posten = await db.vermoegensposten.findMany({ where: { betreuteId: betreute.id } })
|
|
for (const p of posten) {
|
|
const werte: Record<string, { wert: number; quelle: string }> = {
|
|
IMMOBILIE: { wert: 268000, quelle: 'Bodenrichtwert und Vergleichspreise, eigene Schätzung' },
|
|
VERSICHERUNG: { wert: 14820.55, quelle: 'Rückkaufswertmitteilung vom 26.03.2026' },
|
|
KONTO: { wert: 2967.11, quelle: 'Kontoauszug 07/2026' },
|
|
}
|
|
const w = werte[p.art]
|
|
if (w) {
|
|
await db.wertstand.create({
|
|
data: {
|
|
vermoegenspostenId: p.id,
|
|
stichtag: datum('2026-07-31'),
|
|
wertCent: cent(w.wert),
|
|
wertquelle: w.quelle,
|
|
},
|
|
})
|
|
}
|
|
}
|
|
|
|
// --- Sozialhilfeantrag mit Nachforderungen ------------------------------
|
|
const antrag = await db.sozialhilfeAntrag.create({
|
|
data: {
|
|
betreuteId: betreute.id,
|
|
traeger: 'Bezirk Oberfranken',
|
|
leistungsart: 'Hilfe zur Pflege nach dem 7. Kapitel SGB XII',
|
|
aktenzeichen: 'HzP 2026/04711',
|
|
sachbearbeiter: 'Herr Doblinger',
|
|
telefon: '0921 604-1234',
|
|
antragAm: datum('2026-02-26'),
|
|
eingangBeimTraegerAm: datum('2026-03-02'),
|
|
},
|
|
})
|
|
|
|
await db.nachforderung.createMany({
|
|
data: [
|
|
{ antragId: antrag.id, bezeichnung: 'Rentenbescheide aller Träger', status: 'ANERKANNT', angefordertAm: datum('2026-03-18'), uebersandtAm: datum('2026-03-26'), ausCheckliste: true, reihenfolge: 1 },
|
|
{ antragId: antrag.id, bezeichnung: 'Kontoauszüge der letzten 3 Monate, lückenlos', status: 'ANERKANNT', angefordertAm: datum('2026-03-18'), uebersandtAm: datum('2026-03-26'), ausCheckliste: true, reihenfolge: 2 },
|
|
{ antragId: antrag.id, bezeichnung: 'Pflegegradbescheid', status: 'ANERKANNT', angefordertAm: datum('2026-03-18'), uebersandtAm: datum('2026-03-26'), ausCheckliste: true, reihenfolge: 3 },
|
|
{ antragId: antrag.id, bezeichnung: 'Heimvertrag', status: 'UEBERSANDT', angefordertAm: datum('2026-03-18'), uebersandtAm: datum('2026-04-08'), ausCheckliste: true, reihenfolge: 4 },
|
|
{ antragId: antrag.id, bezeichnung: 'Aktueller Grundbuchauszug', status: 'UEBERSANDT', angefordertAm: datum('2026-04-21'), uebersandtAm: datum('2026-05-06'), ausCheckliste: true, reihenfolge: 5 },
|
|
{ antragId: antrag.id, bezeichnung: 'Rückkaufswert Kapitallebensversicherung zum Antragsdatum', status: 'IN_BESCHAFFUNG', angefordertAm: datum('2026-04-21'), fristBis: datum('2026-09-05'), erlaeuterung: 'Zweite Anfrage an die Gesellschaft am 12.08.2026 raus.', ausCheckliste: true, reihenfolge: 6 },
|
|
{ antragId: antrag.id, bezeichnung: 'Notarielle Verträge früherer Grundstücksübertragungen', status: 'OFFEN', angefordertAm: datum('2026-04-21'), fristBis: datum('2026-09-05'), erlaeuterung: 'Der Träger prüft eine Rückforderung wegen Verarmung (§ 528 BGB). Notariat Bayreuth angefragt.', ausCheckliste: true, reihenfolge: 7 },
|
|
{ antragId: antrag.id, bezeichnung: 'Erklärung zu Unterhaltspflichtigen', status: 'OFFEN', angefordertAm: datum('2026-07-30'), fristBis: datum('2026-08-31'), ausCheckliste: true, reihenfolge: 8 },
|
|
],
|
|
})
|
|
|
|
// --- Vorgaenge ----------------------------------------------------------
|
|
await db.vorgang.create({
|
|
data: {
|
|
betreuteId: betreute.id,
|
|
titel: 'Antrag Hilfe zur Pflege beim Bezirk Oberfranken',
|
|
kategorie: 'SOZIALHILFE',
|
|
status: 'WARTET_AUF_DRITTE',
|
|
gegenstelle: 'Bezirk Oberfranken',
|
|
aktenzeichen: 'HzP 2026/04711',
|
|
naechsterSchritt: 'Rückkaufswert und Übertragungsverträge nachreichen, dann Sachstand erfragen',
|
|
letzterKontaktAm: datum('2026-07-30'),
|
|
angelegtVonId: userId,
|
|
beschreibungVerschluesselt: verschluesseln(
|
|
'Antrag vom 26.02.2026. Der Bezirk hat in zwei Wellen nachgefordert. ' +
|
|
'Offen sind noch der Rückkaufswert der Lebensversicherung und die notariellen ' +
|
|
'Übertragungsverträge aus den 1990er-Jahren.',
|
|
'beschreibungVerschluesselt',
|
|
),
|
|
},
|
|
})
|
|
|
|
await db.vorgang.create({
|
|
data: {
|
|
betreuteId: betreute.id,
|
|
titel: 'Haus Bindlach: Verwertung oder Darlehen klären',
|
|
kategorie: 'IMMOBILIE',
|
|
status: 'IN_ARBEIT',
|
|
gegenstelle: 'Bezirk Oberfranken',
|
|
genehmigungspflichtig: true,
|
|
naechsterSchritt: 'Verkehrswert belastbar ermitteln lassen, dann Gespräch mit dem Bezirk',
|
|
angelegtVonId: userId,
|
|
beschreibungVerschluesselt: verschluesseln(
|
|
'Das Haus ist seit dem Heimeinzug leerstehend. Ein Verkauf wäre nach § 1850 BGB ' +
|
|
'genehmigungspflichtig. Vorrangig prüfen: Darlehen nach § 91 SGB XII gegen ' +
|
|
'Grundschuld, damit nicht unter Zeitdruck verkauft werden muss.',
|
|
'beschreibungVerschluesselt',
|
|
),
|
|
},
|
|
})
|
|
|
|
await db.vorgang.create({
|
|
data: {
|
|
betreuteId: betreute.id,
|
|
titel: 'Aufwandspauschale für das erste Betreuungsjahr beantragen',
|
|
kategorie: 'GERICHT',
|
|
status: 'OFFEN',
|
|
gegenstelle: 'Amtsgericht Bayreuth',
|
|
aktenzeichen: 'XVII 412/25',
|
|
naechsterSchritt: 'Antrag nach Ablauf des Betreuungsjahres am 13.02.2027 stellen',
|
|
angelegtVonId: userId,
|
|
},
|
|
})
|
|
|
|
// --- Kontaktprotokolle --------------------------------------------------
|
|
const besuche: { tag: string; dauer: number; inhalt: string; wunsch?: string }[] = [
|
|
{ tag: '2026-06-14', dauer: 75, inhalt: 'Besuch im Heim. Gemeinsam im Garten gesessen. Atmung an diesem Tag ruhig, Sauerstoff 2 l/min. Über das Haus gesprochen.', wunsch: 'Möchte das Haus nicht verkaufen, solange es nicht sein muss.' },
|
|
{ tag: '2026-07-05', dauer: 60, inhalt: 'Besuch. Erschöpft, blieb im Zimmer. Pflegedienstleitung berichtet von zwei kurzen Luftnotepisoden in der Vorwoche.' },
|
|
{ tag: '2026-07-22', dauer: 30, inhalt: 'Telefonat mit dem Sozialdienst wegen der Heimabrechnung Juli.' },
|
|
{ tag: '2026-08-09', dauer: 90, inhalt: 'Besuch mit Enkelin. Deutlich wacher als beim letzten Mal. Wünscht sich häufiger Besuch am Nachmittag.', wunsch: 'Besuche bitte nachmittags, vormittags ist sie zu müde.' },
|
|
]
|
|
|
|
for (const b of besuche) {
|
|
await db.kontaktprotokoll.create({
|
|
data: {
|
|
betreuteId: betreute.id,
|
|
datum: new Date(`${b.tag}T15:00:00.000Z`),
|
|
art: b.dauer === 30 ? 'TELEFONAT' : 'BESUCH',
|
|
dauerMinuten: b.dauer,
|
|
mitBetreuter: b.dauer !== 30,
|
|
inhaltVerschluesselt: verschluesseln(b.inhalt, 'inhaltVerschluesselt'),
|
|
wuenscheVerschluesselt: b.wunsch ? verschluesseln(b.wunsch, 'wuenscheVerschluesselt') : null,
|
|
},
|
|
})
|
|
}
|
|
|
|
// --- Gesundheitsverlauf -------------------------------------------------
|
|
await db.gesundheitsereignis.createMany({
|
|
data: [
|
|
{ betreuteId: betreute.id, datum: datum('2026-03-11'), art: 'EXAZERBATION', titel: 'Infektexazerbation, ambulant behandelt', behandler: 'Dr. Reuther', beschreibungVerschluesselt: verschluesseln('Antibiotikum und kurzzeitig Prednisolon. Keine Einweisung nötig.', 'beschreibungVerschluesselt') },
|
|
{ betreuteId: betreute.id, datum: datum('2026-05-02'), bisDatum: datum('2026-05-09'), art: 'KRANKENHAUS', titel: 'Stationär wegen Luftnot', einrichtung: 'Klinikum Bayreuth', abwesenheitstage: 7, beschreibungVerschluesselt: verschluesseln('Aufnahme über die Notaufnahme. Sauerstoffbedarf danach von 1,5 auf 2 l/min erhöht.', 'beschreibungVerschluesselt') },
|
|
{ betreuteId: betreute.id, datum: datum('2026-06-20'), art: 'HILFSMITTEL', titel: 'Mobiler Sauerstoffkonzentrator bewilligt', beschreibungVerschluesselt: verschluesseln('Ermöglicht Ausflüge aus dem Zimmer.', 'beschreibungVerschluesselt') },
|
|
{ betreuteId: betreute.id, datum: datum('2026-07-15'), art: 'IMPFUNG', titel: 'Pneumokokken-Impfung', behandler: 'Dr. Reuther' },
|
|
],
|
|
})
|
|
|
|
// --- Heimkosten ---------------------------------------------------------
|
|
for (const monat of [5, 6, 7]) {
|
|
await db.heimkostenmonat.create({
|
|
data: {
|
|
betreuteId: betreute.id,
|
|
jahr: 2026,
|
|
monat,
|
|
pflegeEigenanteilCent: cent(1489.0),
|
|
unterkunftVerpflegungCent: cent(878.4),
|
|
investitionskostenCent: cent(486.3),
|
|
ausbildungsumlageCent: cent(112.5),
|
|
pflegekasseCent: cent(1363.0),
|
|
leistungszuschlagProzent: 15,
|
|
leistungszuschlagCent: cent(223.35),
|
|
renteCent: cent(1284.62),
|
|
barbetragCent: cent(135.66),
|
|
bekleidungspauschaleCent: cent(27.13),
|
|
abwesenheitstage: monat === 5 ? 7 : 0,
|
|
},
|
|
})
|
|
}
|
|
|
|
console.log('Demofall "Erika Musterfrau" angelegt.')
|
|
}
|
|
|
|
async function main() {
|
|
const benutzer = await ersterZugang()
|
|
|
|
if (process.env.SEED_DEMO === 'true' && !istProduktion && benutzer) {
|
|
await demofall(benutzer.id)
|
|
} else if (process.env.SEED_DEMO === 'true' && istProduktion) {
|
|
console.log('SEED_DEMO wird in der Produktion nicht ausgefuehrt.')
|
|
}
|
|
|
|
console.log('\nFertig. Weiter geht es im Browser unter /ersteinrichtung.')
|
|
}
|
|
|
|
main()
|
|
.catch((fehler) => {
|
|
console.error('Seed fehlgeschlagen:', fehler)
|
|
process.exit(1)
|
|
})
|
|
.finally(() => db.$disconnect())
|