851724774abdc7a9549b322be57946ab4e1407fe
12
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
18709a8e49 |
Ein Jahr Kontobewegungen im Beispielfall
Der Demofall brachte Konten und Kontostaende mit, aber keine einzige Buchung. Kassenbuch, Belegpflicht, Dauerzahlungen und die Erkennung wiederkehrender Zahlungen liefen darin also leer - wer die Anwendung zum ersten Mal ansah, musste erst eine CSV importieren. Neu sind 107 Buchungen ueber zwoelf Monate, erzeugt aus einem Muster statt einzeln abgetippt: Rente, Betriebsrente und Pflegekasse als Einnahmen, Heimentgelt, Barbetrag, Sterbegeldbeitrag, Telefon und Kontofuehrung monatlich, dazu Rundfunkbeitrag und Grundsteuer vierteljaehrlich sowie eine Handvoll Einzelposten. Was der Fall damit zeigt: - Das Heimentgelt steht in drei Schreibweisen auf dem Auszug und wird trotzdem als eine monatliche Zahlung gefuehrt. - Genau eine Buchung hat weder Quittung noch Begruendung. Eine, nicht zwanzig: Die Liste soll etwas zu zeigen haben und nicht rot leuchten. - Die Sterbegeld-Lastschriften bleiben unzugeordnet, damit sich der Handgriff "zwoelf Zahlungen als belegt zuordnen" ausprobieren laesst. Der Beitrag steigt im Mai von 18,40 auf 19,80 - damit auch die Abweichungspruefung sichtbar wird. - Der Kontostand zum 31.07. wird aus Anfangsstand und Buchungen gerechnet, nicht behauptet. Ein Beispielfall, der sich selbst widerspricht, waere schlimmer als einer ohne Buchungen. Zwei Funde beim Bauen: Der Unique-Index auf (kontoId, importReferenz) hat einen echten Fehler in den Daten gefangen: Zwei Renten am Monatsersten ergaben dieselbe Referenz. Genau dafuer ist er da. "gGmbH" fehlte in der Rechtsformliste. Dadurch galten "Seniorenzentrum St. Katharina" und "... gGmbH" als zwei Empfaenger - und die Gruppe zerfiel ausgerechnet in dem Fachgebiet, in dem fast jeder Traeger eine gGmbH ist. Ergaenzt, mit Test. Geprueft: ein Einheitstest fuer die Rechtsform, vier End-to-End-Tests am Beispielfall - Umfang und Zeitraum, Kontostand gegen Buchungssumme, die erkannten Gruppen und das Kassenbuch. Refs #22 |
||
|
|
848aaf1b9f |
Absenderanschrift in jedem Schreiben
Jeder erzeugte Brief ging bisher ohne Ruecksendeadresse hinaus: Im Absenderblock stand nur der Name, in der Datumszeile fehlte der Ort. Bei einer Versicherung oder Behoerde wirkt das unserioes und macht es der Gegenstelle unnoetig schwer zu antworten. Neu unter "Mein Konto" ein Feld fuer Strasse, PLZ und Ort. Ab da stehen sie im Absenderblock jedes Schreibens, und die Datumszeile lautet "Bayreuth, den 23.08.2026". Die Anschrift haengt am Benutzer, nicht an der betreuten Person: Sie gehoert zu mir und gilt fuer alle Akten. Nicht die Logik fehlte, sondern die Datenquelle - absenderBlock() und datumsZeile() konnten die Felder schon, briefkopfBauen() hat sie mit einem Kommentar auf null gesetzt, weil es sie im Datenmodell nicht gab. Aufbau: - Drei Felder am Benutzer, Migration ueber den Wrapper aus #24. Das war sein erster echter Einsatz: erzeugte Datei ohne Handarbeit brauchbar, DROP-INDEX-Zeilen bereits heraus. - Ein Absender-Typ statt eines blossen Namens, durchgereicht bis in den Serienlauf - der hat bisher {name} gebaut und haette die Anschrift sonst still verloren. - Zwei Platzhalter fuer eigene Vorlagen: betreuer_anschrift, betreuer_ort. Was fehlt, bleibt weg: Jede der drei Angaben ist einzeln freiwillig. Nur ein Ort ergibt eine Datumszeile mit Ort und einen Absenderblock, der weiterhin nur aus dem Namen besteht. Eine halb ausgefuellte Adresse sieht nach Fehler aus, ein blosser Name nach Absicht. Geprueft: sieben Einheitstests fuer den Absenderblock inklusive jeder Teilbefuellung und der Gegenprobe, dass nie die Heimadresse der betreuten Person im Absender landet; vier End-to-End-Tests vom leeren Zustand ueber das Speichern bis zum fertigen Brief. Refs #21 |
||
|
|
e19300b33d |
Migrationskommentare zeigen auf die richtige Datei
Fuenf Migrationen verwiesen fuer die Herkunft der Trigramm-Indizes auf prisma/sql/suche.sql. Diese Datei gibt es nicht; sie heisst volltextsuche.sql. Der Hinweis steht genau dort, wo ihn jemand liest, der die naechste Migration erzeugt - ein falscher Pfad schickt ihn ins Leere. Dazu ein Verweis auf das Werkzeug aus #24: "npm run db:migration" nimmt die Zeilen inzwischen selbst heraus, die Kommentare rieten aber weiterhin nur zum Nachsehen. Geprueft, bevor die Dateien angefasst wurden: Prisma speichert zwar eine Pruefsumme je Migration, "migrate deploy" verifiziert sie fuer bereits angewendete Migrationen aber nicht. Nachgestellt mit genau der Lage, die auch auf dem Server entsteht - Pruefsumme in der Datenbank weicht ab, "migrate status" und "migrate deploy" bleiben zufrieden. Refs #25 |
||
|
|
9e68d10787 |
Ansprechpartner am Vorgang hinterlegen
Ein namentlich adressiertes Schreiben landet bei der Person, die den Vorgang kennt, statt in der Poststelle. Am Vorgang laesst sich jetzt ein Kontakt aus dem Verzeichnis als Ansprechpartner benennen; die Sachstandsanfrage redet ihn an, und sein Name steht ueber der Behoerde. Die Sachstandsanfrage konnte das schon - sie bekam den Namen nur nie uebergeben. entwurf.ts lud einen der verknuepften Kontakte und verwarf ihn wieder. Wer angeredet wird, ist jetzt eine Entscheidung und kein Zufall. Aufbau: - Migration: ansprechpartnerId am Vorgang (SetNull - ein Vorgang darf nicht daran haengen, dass es den Kontakt noch gibt) und anrede am Kontakt. - Die Anrede als reine Funktion in kontakte/anrede.ts. Geraten wird nie: ohne hinterlegte Anrede wird mit vollem Namen angeredet. Ein leerer Name mit gesetzter Anrede faellt auf die allgemeine Anrede zurueck - "Sehr geehrte Frau ," darf nie herausgehen. - Der Wechsel des Ansprechpartners steht mit NAMEN im Protokoll. Zwei cuids nebeneinander waeren protokolliert, aber nicht nachvollziehbar, und nachvollziehbar war die Anforderung. - Der Ansprechpartner wird gegen die Akte geprueft, bevor er gespeichert wird - sein Name steht anschliessend im Briefkopf. Nebenbei behoben: Der Schreiben-Editor verwarf die empfaengerAnschrift aus dem Entwurf, sein State startete immer leer. Jetzt kommt die Anschrift des Ansprechpartners im Formular an. Geprueft: neun Einheitstests fuer die Anrede, fuenf End-to-End-Tests von der Auswahl bis zum Protokolleintrag. Refs #9 |
||
|
|
f8d7752c27 |
Eigene Vorlagen fuer Anschreiben
Wer zum dritten Mal denselben Brief tippt, tippt ihn beim vierten Mal
falsch. Neu unter Einstellungen -> Schreibvorlagen: eigene Texte
hinterlegen, beim freien Schreiben auswaehlen, Platzhalter fuellen sich
aus den Daten, die ohnehin im System stehen.
Aufbau:
- Die Ersetzung als reine Funktionen in schreiben/platzhalter.ts. Drei
Faelle, keiner davon still: Wert einsetzen, sichtbare Luecke
[Aktenzeichen des Gerichts], unbekannter Platzhalter markiert. Ein
Tippfehler in der Vorlage darf das Schreiben nicht verhindern - aber
er muss ins Auge springen.
- Die Bestellungsabsatz-Warnung ist zweistufig: Der erste Speicherversuch
ohne ihn kommt mit einer Rueckfrage zurueck, erst die Bestaetigung
speichert. Ohne den Nachweis der Vertretungsmacht kommt keine Auskunft,
und das faellt erst nach vier Wochen auf. Ein ausformulierter Absatz
zaehlt so viel wie der Platzhalter, ein halber Nachweis ("Beschluss der
Eigentuemerversammlung") nicht.
- Vier mitgelieferte Vorlagen bleiben Code und damit unveraenderlich;
"Als Kopie uebernehmen" legt eine eigene Fassung an. Fuer Rueckkaufswert
und Kuendigung fuehrt die Oberflaeche weiter zuerst zu den
fallbezogenen Erzeugern - die rechnen Kuendigungstermine aus, was eine
Vorlage nicht kann.
- Vorlagen haengen an der betreuten Person, nicht am Benutzer: Sie
beziehen sich auf ihren Fall, liegen im Papierkorb wie alles andere und
sind im Vollexport ihrer Akte enthalten.
Der Editor haelt Betreff und Text als eigenen State. Beim Wechsel der
Vorlage ueberlebte der alte Entwurf die Navigation, die Vorlage kam nie
im Textfeld an - ein key auf dem Editor setzt ihn neu auf. Gefunden hat
das erst der End-to-End-Test.
Geprueft: dreizehn Einheitstests fuer die Ersetzung, sechs
End-to-End-Tests fuer Warnung, Kopie, Aufloesung und Papierkorb.
Refs #7
|
||
|
|
fa994350d6 |
Dauerzahlungen zu einem Vertrag gelten als belegt
Bei einer monatlichen Praemie gibt es keinen Beleg - es gibt einen Vertrag, und der wird einmal abgeschlossen, nicht monatlich quittiert. Bisher blieb nur ein Freitextfeld pro Buchung: zwoelfmal im Jahr derselbe Satz, mal Anzahl der Vertraege. Buchung.vertragId mit onDelete: SetNull. Der Belegzustand kennt jetzt drei Faelle statt zwei, getrennt ausgewiesen - ein Vertrag belegt, eine Begruendung erklaert nur; zusammengeworfen verliert die Belegquote ihre Aussage gegenueber dem Rechtspfleger. Dazu: automatische Zuordnung beim Import bei eindeutiger Gegenstelle, Sammelzuordnung ueber die Gruppen aus Issue #3, und die Rechnungslegung nennt den Vertrag samt Nummer statt "ohne Beleg". Die Sicherung dagegen: Weicht der Betrag um mehr als ein Fuenftel vom hinterlegten Beitrag ab, faellt die Anerkennung weg und die Buchung wird als "Betrag pruefen" markiert. Sonst waere die Zuordnung eine Hintertuer. Zur Toleranz - ein Entwurf und seine Korrektur: Zuerst verglich ich den Betrag gegen alle ueblichen Zahlweisen, damit es auch passt, wenn die Bank anders abbucht als der Vertrag es fuehrt. Der eigene Test hat das widerlegt: Bei 42,50 monatlich liegen 420 innerhalb von 20 Prozent des Jahresbetrags von 510 - ausgerechnet der Fall aus der Aufgabenstellung waere durchgerutscht. Jetzt wird stur gegen den hinterlegten Beitrag verglichen. Bucht die Bank anders ab, stimmen die Vertragsdaten nicht, und das soll auffallen. Behoben: Die Buchungsliste hatte eine eigene, fast gleiche Datenabfrage neben der aus berichte/gemeinsam.ts. Nur eine von beiden wurde nachgezogen, in der Liste stand weiter "Beleg fehlt". Beide benutzen jetzt BUCHUNG_EINBEZUG und zuBuchungEingabe - das kann nicht halb vergessen werden. Gefunden hat es der Browsertest. Nebenbei: Die Roadmap trug noch "Aktuelle Fassung 1.1.2" - Nachwirkung der parallelen Bearbeitung von #3 und #10. Nachgezogen wie zugesagt. Geprueft: 23 neue Einheitstests, die Haelfte davon darauf gerichtet, dass die Anerkennung auch wirklich wegfaellt. Fuenf End-to-End-Tests auf beiden Geraeteprofilen. 560 Unit-Tests, 63 e2e, null Typfehler. Refs #11 |
||
|
|
aa0cc66292 |
Wiederkehrende Zahlungen aufspueren
Am Anfang einer Betreuung weiss niemand, welche Vertraege es gibt. Die Antwort steht aber im Kontoauszug: Wer regelmaessig abbucht, hat einen Vertrag. Neu unter Vermoegen & Kasse wird aus hunderten Buchungszeilen eine Arbeitsliste. Aufbau: - Die Erkennung liegt als reine Funktionen in src/lib/finanzen/wiederkehrend.ts, ohne Datenbank. Nur so laesst sich jede Einordnung einzeln nachrechnen. - Der Datenzugriff steht getrennt in wiederkehrend-laden.ts. - Ein kleines Modell haelt fest, was bewusst KEIN Vertrag ist. Es haengt am normalisierten Empfaengernamen, nicht an Buchungen - nur so ueberlebt die Entscheidung den naechsten Import. Zwei Haltungen liegen der Erkennung zugrunde: Im Zweifel getrennt lassen. Zusammengefasst wird nur das Unstrittige: Gross- und Kleinschreibung, Satzzeichen, Umlaute, Rechtsform am Ende. Keine Aehnlichkeitssuche - "Stadtwerke Bamberg" und "Stadtwerke Bayreuth" trennen drei Zeichen und zwei Rechnungssteller. Zwei Zeilen zu viel sind harmlos; eine falsche Gruppe verbirgt einen Vertrag. Nichts behaupten, was nicht belegt ist. Statt "monatlich" steht dort "6 Zahlungen, etwa monatlich". Schwankt der Betrag, wird die Spanne genannt. Ohne erkennbaren Abstand wird nicht hochgerechnet, sondern gezeigt, was geflossen ist. Dazu eine zweite Liste fuer einzelne groessere Zahlungen: Die Gruppierung braucht zwei Zahlungen, ein Jahresbeitrag erscheint bei einem importierten Jahr aber nur einmal - und ausgerechnet Versicherungen zahlt man oft jaehrlich. Geprueft: 37 Einheitstests, die Haelfte davon in der Gegenrichtung, plus vier End-to-End-Tests auf beiden Geraeteprofilen. Der Browsertest hat zwei Fehler gefunden, die Typpruefung und Einheitstests durchgelassen haben - darunter ein verlorener Backslash, der die Vorbelegung des Beitrags stillschweigend verschluckte. Nebenbei: .claude/ ins .gitignore. Dort legen parallel arbeitende Agenten ihre Worktrees ab; ein "git add -A" haette einen kompletten zweiten Checkout eingecheckt. Refs #3 |
||
|
|
c38c988c3c |
feat: Anmelden mit Passkey
Passkeys (WebAuthn/FIDO2) als zweiter Anmeldeweg - auf der Anmeldeseite und bei der erneuten Bestaetigung vor sensiblen Aktionen. Verwaltet unter "Mein Konto". Der Passkey ERSETZT den zweiten Faktor, statt zu ihm hinzuzukommen. Das ist tragbar, weil er zwei Faktoren in sich vereint: Besitz des Geraets und dessen Entsperrung. Damit das keine Annahme bleibt, wird die Nutzerverifikation angefordert (userVerification: 'required') und serverseitig nachgeprueft - eine Antwort ohne sie wird abgewiesen. TOTP muss vorher eingerichtet sein. Ein Passkey haengt an einem Geraet; waere er der einzige Weg hinein, koennte ein verlorenes Handy die Akte schliessen. Aufbau: - Die Pruefung laeuft vollstaendig im Credentials-Provider "passkey", nicht in der Server Action davor. Ein "war schon geprueft" ueber die Grenze zu reichen waere die Stelle, an der sich Anmeldungen faelschen lassen. - Challenges liegen serverseitig und werden per DELETE genau einmal verbraucht - die Datenbank entscheidet, wer sie bekommt. - Bereichskennung und Herkunft kommen aus AUTH_URL, nicht aus einer zweiten Konfigurationsquelle. - Ohne https wird der Knopf gar nicht erst angeboten. - Rate-Limiting, Kontosperre und Audit-Log gelten unveraendert; die Anmeldung nennt den verwendeten Passkey namentlich. Zwei Anmerkungen zur Migration: - "prisma migrate diff" wollte zusaetzlich die Trigramm-Indizes aus prisma/sql/suche.sql loeschen, die Prisma nicht kennt. Die Zeilen sind entfernt und im Migrationskopf vermerkt - ein DROP haette die unscharfe Suche stillgelegt, ohne dass etwas fehlgeschlagen waere. - "prisma format" hat das Schema durchgaengig neu ausgerichtet. Ohne Whitespace betrachtet sind es 88 hinzugefuegte Zeilen und keine geloeschte. Geprueft: 21 Einheitstests, drei End-to-End-Tests gegen Chromiums virtuellen Authenticator (einrichten, anmelden, bestaetigen, entfernen, Geraet ohne Verifikation abgewiesen), beide Geraeteprofile. 496 Tests, null Typfehler, Produktionsbau steht. Nebenbei: Die Zahlen im README waren veraltet (24 statt 33 Modelle, 46 statt 79 Seiten). Sie stehen jetzt mit dem Befehl daneben, mit dem man sie nachzaehlt. |
||
|
|
7780739a1a |
fix: Ersteinrichtung liess sich nicht abschliessen, Betreute nicht loeschen
Zwei Fehler, beide erst im Betrieb sichtbar. 1. Schritt 3 der Ersteinrichtung meldete "Bitte pruefe die markierten Felder" - und markierte keines. Man kam dort nicht weiter. Das kurze Formular schickt neun Felder, das vollstaendige unter Stammdaten alle. Beide gehen durch dasselbe Schema, und die optionalen Felder waren zwar nullable, aber nicht optional: Ein Feld, das gar nicht abgeschickt wurde, ist undefined, nicht null. Beanstandet wurden also zwei Dutzend Felder, die auf dem Bildschirm nie standen. Damit das nicht wiederkommt, unterscheidet die Aktion jetzt zwischen Eingabefehler und Formular-Schema-Bruch: Beanstandet die Pruefung ein Feld, das nicht abgeschickt wurde, sagt sie das ausdruecklich und schreibt es ins Serverlog, statt es dem Nutzer als Eingabefehler unterzuschieben, den er nirgends finden kann. 2. Eine betreute Person liess sich nicht endgueltig loeschen. Postgres setzt beim Loeschen den Verweis im Audit-Log auf NULL, und genau dieses UPDATE hat der Unveraenderlichkeits-Trigger abgewiesen. Damit scheiterte auch das Leeren des Papierkorbs nach 30 Tagen. Der Trigger erlaubt jetzt genau eine Ausnahme: das Nullen der beiden Fremdschluessel. Jede andere Aenderung bleibt verboten, auch das Setzen eines Fremdschluessels auf einen anderen Wert. Der Nachweis bleibt vollstaendig - es verschwindet nur der Verweis auf einen Datensatz, den es nicht mehr gibt. Geprueft: sieben neue Tests gegen die tatsaechlichen Formularinhalte, der Ablauf einmal komplett im Browser bis zum Dashboard, und der Trigger gegen eine laufende Datenbank - Loeschen gelingt, Faelschen scheitert. Etappe: 8 Phase: Haertung |
||
|
|
1234a57fa6 |
fix: Typisierung von oeffnen(), CSP im Entwicklungsmodus, Nonce-Abweichung
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
|
||
|
|
22b3a9b9e9 |
feat: Stammdaten, Ersteinrichtung, Zweitfaktor und Protokollansicht
Damit ist Etappe 1 fachlich vollstaendig. Ersteinrichtung in drei Schritten, bewusst ausserhalb des Anwendungsrahmens: der setzt Benutzer, zweiten Faktor UND eine betreute Person voraus - beim allerersten Start gibt es nichts davon. Die Aktion fuer den ersten Zugang sperrt sich selbst, sobald ein Benutzer existiert. Zweiter Faktor: Das Geheimnis wird zuerst nur abgelegt, ohne totpAktiviertAm. Erst ein echter Code aus der App schaltet ihn scharf. Wer sich beim Abtippen vertut oder die falsche App nimmt, wuerde sich sonst dauerhaft aussperren. Erst danach gibt es die Wiederherstellungscodes - einmal angezeigt, danach nur noch als Hash. Betreuungsjahr (src/lib/stammdaten/betreuungsjahr.ts): laeuft ab dem Tag der Bestellung, nicht ab dem Kalenderjahr. Schrittweise vorwaerts gerechnet statt ueber eine Differenz, damit Schaltjahre und der 29. Februar von selbst stimmen. Daran haengen Jahresbericht, Rechnungslegung und die Ausschlussfrist der Aufwandspauschale. Aendert sich das Bestellungsdatum, werden die Pflichtfristen neu abgeleitet - ueber einen dynamischen Import, damit die Stammdaten auch dann speicherbar bleiben, wenn das Fristenmodul fehlt. Protokollansicht zeigt je Eintrag die geaenderten Felder als alt -> neu. Die Aenderungen liegen verschluesselt, weil dort Diagnosen und Kontostaende stehen koennen. Seed legt bewusst keine echten Personendaten an, nur den ersten Zugang. Mit SEED_DEMO=true kommt ein vollstaendiger Beispielfall dazu - Erika Musterfrau, COPD GOLD III, Pflegegrad 3, Heimeinzug Januar 2026, leerstehendes Haus, zwei Versicherungen, laufender Antrag beim Bezirk mit acht Nachforderungen. Nur ausserhalb der Produktion. Docker Compose ohne Reverse Proxy - der ist auf dem Zielserver vorhanden. Die Datenbank hat keine Portfreigabe und ist nur ueber das interne Netz erreichbar; die Anwendung horcht auf 127.0.0.1. Etappe: 1 Phase: Fundament |
||
|
|
6c1915ab9c |
feat: Vollstaendiges Datenmodell als Prisma-Schema
Alle Fachentitaeten der Betreuung in einem Schema - bewusst komplett, damit spaetere Etappen keine Migration am Fundament brauchen. Kernentscheidungen: - Geldbetraege durchgaengig als BigInt in Cent. Eine Rechnungslegung nach § 1865 BGB darf keine Rundungsfehler erben. - Felder mit Suffix Verschluesselt tragen einen AES-256-GCM-Umschlag. Wo trotzdem gesucht werden muss (IBAN), steht ein HMAC-Blindindex daneben. - Fristen und Stichtage sind @db.Date, nicht DateTime - sonst verschiebt die Zeitzone eine Frist um einen Tag. - Frist.systemSchluessel macht die aus dem Bestellungsdatum abgeleiteten Pflichtfristen idempotent. - Genehmigung.beschlussDokumentId ist die technische Voraussetzung dafuer, dass ein Vorgang ohne Beschluss nicht auf VOLLZOGEN gesetzt werden kann. - DokumentVerknuepfung mit echten Fremdschluesseln statt loser Typ/ID- Spalte: eine Akte muss referenzsicher sein. - Wertstand und Saldostand sind Historien - Werte werden nie ueberschrieben. Dazu zwei SQL-Skripte, die Prisma nicht abbilden kann: - volltextsuche.sql: tsvector-Trigger (deutsch) mit Gewichtung A/B/C, plus Trigram-Indizes fuer unscharfe Absendersuche. - audit-schutz.sql: Trigger, die UPDATE und DELETE auf audit_log verweigern. Das Log ist Nachweis gegenueber Gericht und Miterben, also nur anhaengend - auch gegen psql. Etappe: 1 Phase: Fundament |