Phase 9: Kassenverwaltung
Kassenbuch, Mitgliedsbeitraege, Rechnungen an Dritte, Kassenpruefung und die Auswertungen fuer die Jahreshauptversammlung. Die drei Festlegungen aus Phase-9.md sitzen in der Datenbank, nicht im Router: eine festgeschriebene Buchung ist unveraenderbar, ein geprueftes Jahr nimmt kein Datum mehr an, und gerechnet wird mit Prisma.Decimal. Korrigiert wird ueber eine Gegenbuchung mit Verweis - beide Buchungen bleiben stehen, damit im Kassenbuch zu sehen ist, dass etwas berichtigt wurde. Neun Modelle, drei Trigger, zwoelf CHECKs, sechs Router, fuenf Ansichten. Dazu der Bankimport fuer CAMT.053 und MT940 ohne Fremdbibliothek, der Kategorievorschlag aus dem eigenen Buchungsbestand und die Bruecke von Phase 8: aus einem gegengezeichneten Beleg wird eine Buchung, das Foto haengt danach an ihr. Nebenbei behoben: die Rechnungsnummer war ein Wettlauf, und eine Organisation liess sich nicht mehr loeschen, sobald sie eine festgeschriebene Buchung hatte - der Trigger kannte den Aufbewahrungsschalter nicht. 562 Unit-Tests und 239 E2E-Tests gruen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
d70acad4cd
commit
4165334c5a
@@ -234,6 +234,17 @@ model Organisation {
|
||||
diktate Diktat[]
|
||||
belegentwuerfe Belegentwurf[]
|
||||
|
||||
// Phase 9 — Kassenverwaltung
|
||||
kassenkonten Kassenkonto[]
|
||||
buchungskategorien Buchungskategorie[]
|
||||
buchungen Buchung[]
|
||||
beitragssaetze Beitragssatz[]
|
||||
beitraege Beitrag[]
|
||||
rechnungen Rechnung[]
|
||||
rechnungspositionen Rechnungsposition[]
|
||||
kassenabschluesse Kassenabschluss[]
|
||||
abschlussposten Abschlussposten[]
|
||||
|
||||
@@index([aktiv])
|
||||
@@map("organisation")
|
||||
}
|
||||
@@ -356,6 +367,14 @@ model Person {
|
||||
erfassteBelege Belegentwurf[] @relation("BelegentwurfErfasser")
|
||||
bestaetigteBelege Belegentwurf[] @relation("BelegentwurfBestaetiger")
|
||||
|
||||
// Phase 9 — Kassenverwaltung
|
||||
buchungen Buchung[] @relation("BuchungPerson")
|
||||
erfassteBuchungen Buchung[] @relation("BuchungErfasser")
|
||||
beitraege Beitrag[] @relation("BeitragPerson")
|
||||
gepruefteAbschluesse Kassenabschluss[] @relation("AbschlussPruefer")
|
||||
zweitgepruefteAbschluesse Kassenabschluss[] @relation("AbschlussZweitpruefer")
|
||||
erstellteRechnungen Rechnung[] @relation("RechnungErsteller")
|
||||
|
||||
/// E-Mail und OIDC-Subject sind je Wehr eindeutig, nicht global — dieselbe
|
||||
/// Person kann in zwei Wehren Mitglied sein.
|
||||
@@unique([organisationId, email])
|
||||
@@ -602,6 +621,11 @@ model Datei {
|
||||
alsDiktataufnahme Diktat[] @relation("DiktatAufnahme")
|
||||
alsBelegfoto Belegentwurf[] @relation("BelegentwurfFoto")
|
||||
|
||||
// Phase 9
|
||||
alsBuchungsbeleg Buchung[] @relation("BuchungBeleg")
|
||||
alsRechnungsPdf Rechnung[] @relation("RechnungPdf")
|
||||
alsKassenpruefbericht Kassenabschluss[] @relation("AbschlussBericht")
|
||||
|
||||
@@unique([bucket, objektSchluessel])
|
||||
@@index([organisationId, entitaet, entitaetId])
|
||||
@@index([organisationId, kategorie])
|
||||
@@ -3601,7 +3625,11 @@ model Belegentwurf {
|
||||
geaendertAm DateTime @updatedAt @db.Timestamptz(3)
|
||||
geloeschtAm DateTime? @db.Timestamptz(3)
|
||||
|
||||
/// Die Buchung, die daraus geworden ist (Phase 9). Leer heißt: noch keine.
|
||||
buchungId String? @db.Uuid
|
||||
|
||||
organisation Organisation @relation(fields: [organisationId], references: [id], onDelete: Cascade)
|
||||
buchung Buchung? @relation(fields: [buchungId], references: [id], onDelete: SetNull)
|
||||
fotoDatei Datei @relation("BelegentwurfFoto", fields: [fotoDateiId], references: [id], onDelete: Restrict)
|
||||
kiVorgang KiVorgang? @relation(fields: [kiVorgangId], references: [id], onDelete: SetNull)
|
||||
erfasstVon Person? @relation("BelegentwurfErfasser", fields: [erfasstVonId], references: [id], onDelete: SetNull)
|
||||
@@ -3611,3 +3639,435 @@ model Belegentwurf {
|
||||
@@index([organisationId, belegdatum])
|
||||
@@map("belegentwurf")
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Phase 9 — Kassenverwaltung
|
||||
//
|
||||
// Drei Festlegungen bestimmen alles Weitere. Sie stehen hier, weil sie sich
|
||||
// im Schema abbilden und nicht bloß in der Anwendung durchgesetzt werden:
|
||||
//
|
||||
// 1. **Beträge sind DECIMAL(12,2), niemals Fließkomma.** 0,1 + 0,2 ist in
|
||||
// binärer Fließkommadarstellung nicht 0,3, und eine Kasse, die sich um
|
||||
// Cent verrechnet, ist keine Kasse. Zwölf Stellen reichen für zehn
|
||||
// Milliarden — mehr als jede Wehr je bewegt.
|
||||
//
|
||||
// 2. **Eine festgeschriebene Buchung ist unveränderbar.** Korrekturen laufen
|
||||
// über eine Gegenbuchung mit Verweis auf das Original. Das ist keine
|
||||
// Förmlichkeit: ein Kassenbuch, in dem man rückwirkend ändern kann, ist
|
||||
// vor der Kassenprüfung wertlos. Der Trigger in der Migration setzt es
|
||||
// durch.
|
||||
//
|
||||
// 3. **Der Kassenabschluss friert seinen Zeitraum ein.** Danach nimmt die
|
||||
// Buchungsmaske kein Datum daraus mehr an. Sonst hätte der geprüfte
|
||||
// Bestand am Tag nach der Prüfung eine andere Zahl.
|
||||
//
|
||||
// Der Betrag ist immer **positiv**; die Richtung steht in `art`. Ein
|
||||
// vorzeichenbehafteter Betrag wäre kürzer, aber jede Auswertung müsste dann
|
||||
// darauf vertrauen, dass das Vorzeichen zur Kategorie passt — und beim ersten
|
||||
// Tippfehler stimmt die Summe nicht mehr.
|
||||
// =============================================================================
|
||||
|
||||
/// Welche Art von Konto.
|
||||
enum KontoArt {
|
||||
/// Die Barkasse im Gerätehaus. Wird bei der Prüfung gezählt.
|
||||
BAR
|
||||
/// Ein Bankkonto. Wird gegen den Auszug geprüft.
|
||||
BANK
|
||||
/// Rücklage für eine bestimmte Anschaffung — getrennt geführt, damit
|
||||
/// niemand versehentlich das Geld für das neue Fahrzeug verbraucht.
|
||||
RUECKLAGE
|
||||
}
|
||||
|
||||
/// Geht Geld ein oder hinaus.
|
||||
enum BuchungsArt {
|
||||
EINNAHME
|
||||
AUSGABE
|
||||
}
|
||||
|
||||
/// Wie es um einen Beitrag steht.
|
||||
enum BeitragStand {
|
||||
OFFEN
|
||||
BEZAHLT
|
||||
/// Erlassen — bei Härtefällen, Ehrenmitgliedern, Jugendlichen.
|
||||
ERLASSEN
|
||||
GEMAHNT
|
||||
}
|
||||
|
||||
/// Wie es um eine Rechnung steht.
|
||||
enum RechnungStand {
|
||||
ENTWURF
|
||||
VERSENDET
|
||||
BEZAHLT
|
||||
GEMAHNT
|
||||
STORNIERT
|
||||
}
|
||||
|
||||
/// Woraus eine Rechnung an Dritte entsteht.
|
||||
enum RechnungsAnlass {
|
||||
/// Böswilliger oder fahrlässig ausgelöster Fehlalarm.
|
||||
FEHLALARM
|
||||
/// Ölspur, auslaufende Betriebsstoffe — nach Verursacherprinzip.
|
||||
OELEINSATZ
|
||||
/// Absicherung, Brandsicherheitswache bei einer Veranstaltung.
|
||||
VERANSTALTUNG
|
||||
/// Vermietung von Gerät oder Räumen.
|
||||
VERMIETUNG
|
||||
SONSTIGES
|
||||
}
|
||||
|
||||
/// Ein Konto der Wehr.
|
||||
model Kassenkonto {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
organisationId String @db.Uuid
|
||||
|
||||
bezeichnung String @db.VarChar(120)
|
||||
art KontoArt @default(BAR)
|
||||
|
||||
/// Nur bei Bankkonten. Wird in Auswertungen nie vollständig angezeigt.
|
||||
iban String? @db.VarChar(34)
|
||||
bic String? @db.VarChar(11)
|
||||
|
||||
/// Der Bestand zu Beginn der Aufzeichnung. Alles Weitere ergibt sich aus
|
||||
/// den Buchungen — er wird nie nachträglich „korrigiert".
|
||||
anfangsbestand Decimal @default(0) @db.Decimal(12, 2)
|
||||
|
||||
bemerkung String? @db.VarChar(500)
|
||||
aktiv Boolean @default(true)
|
||||
|
||||
erstelltAm DateTime @default(now()) @db.Timestamptz(3)
|
||||
geaendertAm DateTime @updatedAt @db.Timestamptz(3)
|
||||
geloeschtAm DateTime? @db.Timestamptz(3)
|
||||
|
||||
organisation Organisation @relation(fields: [organisationId], references: [id], onDelete: Cascade)
|
||||
buchungen Buchung[]
|
||||
posten Abschlussposten[]
|
||||
|
||||
@@unique([organisationId, bezeichnung])
|
||||
@@index([organisationId, aktiv])
|
||||
@@map("kassenkonto")
|
||||
}
|
||||
|
||||
/// Wofür Geld ausgegeben oder eingenommen wurde.
|
||||
///
|
||||
/// Zweistufig: eine Oberkategorie („Fahrzeuge") mit Unterkategorien
|
||||
/// („Kraftstoff", „Reparatur"). Mehr Ebenen braucht eine Wehr nicht, und jede
|
||||
/// weitere macht die Auswertung unlesbar.
|
||||
model Buchungskategorie {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
organisationId String @db.Uuid
|
||||
|
||||
name String @db.VarChar(120)
|
||||
art BuchungsArt
|
||||
|
||||
/// Die Oberkategorie. Leer heißt: das hier ist eine.
|
||||
oberkategorieId String? @db.Uuid
|
||||
|
||||
/// Nummer im Kontenrahmen der Gemeinde — für den Export an die Kämmerei.
|
||||
kontonummer String? @db.VarChar(20)
|
||||
|
||||
bemerkung String? @db.VarChar(500)
|
||||
aktiv Boolean @default(true)
|
||||
|
||||
erstelltAm DateTime @default(now()) @db.Timestamptz(3)
|
||||
geaendertAm DateTime @updatedAt @db.Timestamptz(3)
|
||||
geloeschtAm DateTime? @db.Timestamptz(3)
|
||||
|
||||
organisation Organisation @relation(fields: [organisationId], references: [id], onDelete: Cascade)
|
||||
oberkategorie Buchungskategorie? @relation("Kategoriebaum", fields: [oberkategorieId], references: [id], onDelete: SetNull)
|
||||
unterkategorien Buchungskategorie[] @relation("Kategoriebaum")
|
||||
buchungen Buchung[]
|
||||
|
||||
@@unique([organisationId, name])
|
||||
@@index([organisationId, art])
|
||||
@@map("buchungskategorie")
|
||||
}
|
||||
|
||||
/// Eine Buchung.
|
||||
///
|
||||
/// Der Betrag ist positiv, die Richtung steht in `art`. Festgeschrieben heißt:
|
||||
/// unveränderbar und nicht löschbar — die Datenbank hält dagegen. Wer sich
|
||||
/// vertan hat, bucht dagegen; die Gegenbuchung verweist auf das Original, und
|
||||
/// beide bleiben stehen.
|
||||
model Buchung {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
organisationId String @db.Uuid
|
||||
|
||||
kontoId String @db.Uuid
|
||||
kategorieId String @db.Uuid
|
||||
|
||||
/// Das Buchungsdatum, nicht der Zeitpunkt der Erfassung. Danach richtet
|
||||
/// sich, in welchen Abschluss die Buchung fällt.
|
||||
datum DateTime @db.Date
|
||||
art BuchungsArt
|
||||
betrag Decimal @db.Decimal(12, 2)
|
||||
|
||||
verwendungszweck String @db.VarChar(300)
|
||||
belegnummer String? @db.VarChar(60)
|
||||
|
||||
/// Wer das Geld bekommen oder gegeben hat. Entweder eine Person der Wehr
|
||||
/// **oder** ein Dritter als Text — nie beides.
|
||||
personId String? @db.Uuid
|
||||
lieferant String? @db.VarChar(200)
|
||||
|
||||
/// Bei einer Umbuchung zwischen zwei eigenen Konten das Gegenkonto.
|
||||
gegenkontoId String? @db.Uuid
|
||||
|
||||
belegDateiId String? @db.Uuid
|
||||
|
||||
/// Festgeschrieben: ab hier unveränderbar.
|
||||
festgeschriebenAm DateTime? @db.Timestamptz(3)
|
||||
|
||||
/// Die Buchung, die diese hier aufhebt — und umgekehrt.
|
||||
storniertVonId String? @db.Uuid
|
||||
|
||||
erfasstVonId String? @db.Uuid
|
||||
erstelltAm DateTime @default(now()) @db.Timestamptz(3)
|
||||
geaendertAm DateTime @updatedAt @db.Timestamptz(3)
|
||||
|
||||
organisation Organisation @relation(fields: [organisationId], references: [id], onDelete: Cascade)
|
||||
konto Kassenkonto @relation(fields: [kontoId], references: [id], onDelete: Restrict)
|
||||
kategorie Buchungskategorie @relation(fields: [kategorieId], references: [id], onDelete: Restrict)
|
||||
person Person? @relation("BuchungPerson", fields: [personId], references: [id], onDelete: SetNull)
|
||||
erfasstVon Person? @relation("BuchungErfasser", fields: [erfasstVonId], references: [id], onDelete: SetNull)
|
||||
belegDatei Datei? @relation("BuchungBeleg", fields: [belegDateiId], references: [id], onDelete: SetNull)
|
||||
storniertVon Buchung? @relation("Stornierung", fields: [storniertVonId], references: [id], onDelete: SetNull)
|
||||
storniert Buchung[] @relation("Stornierung")
|
||||
beitraege Beitrag[]
|
||||
rechnungen Rechnung[]
|
||||
belegentwurf Belegentwurf[]
|
||||
|
||||
@@index([organisationId, datum(sort: Desc)])
|
||||
@@index([organisationId, kontoId, datum])
|
||||
@@index([organisationId, kategorieId])
|
||||
@@map("buchung")
|
||||
}
|
||||
|
||||
/// Was ein Mitglied im Jahr zahlt.
|
||||
///
|
||||
/// Je Abteilung ein Satz: Aktive zahlen anderes als Jugend, Ehrenmitglieder
|
||||
/// oft nichts. Der Satz gilt ab einem Jahr und wird nicht rückwirkend
|
||||
/// geändert — sonst stimmen die Beiträge vergangener Jahre nicht mehr.
|
||||
model Beitragssatz {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
organisationId String @db.Uuid
|
||||
|
||||
bezeichnung String @db.VarChar(120)
|
||||
betrag Decimal @db.Decimal(12, 2)
|
||||
|
||||
/// Für welche Abteilung. Leer heißt: für alle, die keinen eigenen Satz haben.
|
||||
abteilung String? @db.VarChar(60)
|
||||
|
||||
/// Ab welchem Jahr er gilt.
|
||||
abJahr Int
|
||||
|
||||
aktiv Boolean @default(true)
|
||||
|
||||
erstelltAm DateTime @default(now()) @db.Timestamptz(3)
|
||||
geaendertAm DateTime @updatedAt @db.Timestamptz(3)
|
||||
|
||||
organisation Organisation @relation(fields: [organisationId], references: [id], onDelete: Cascade)
|
||||
beitraege Beitrag[]
|
||||
|
||||
@@unique([organisationId, abteilung, abJahr])
|
||||
@@index([organisationId, abJahr])
|
||||
@@map("beitragssatz")
|
||||
}
|
||||
|
||||
/// Der Beitrag einer Person für ein Jahr.
|
||||
model Beitrag {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
organisationId String @db.Uuid
|
||||
|
||||
personId String @db.Uuid
|
||||
jahr Int
|
||||
|
||||
satzId String? @db.Uuid
|
||||
betrag Decimal @db.Decimal(12, 2)
|
||||
|
||||
stand BeitragStand @default(OFFEN)
|
||||
|
||||
/// Die Buchung, mit der bezahlt wurde. Erst sie macht aus „bezahlt" eine
|
||||
/// Zahl in der Kasse.
|
||||
buchungId String? @db.Uuid
|
||||
bezahltAm DateTime? @db.Date
|
||||
|
||||
/// Wie oft gemahnt wurde. Null heißt: noch nicht.
|
||||
mahnstufe Int @default(0)
|
||||
gemahntAm DateTime? @db.Date
|
||||
/// Bei Erlass: warum. Ein erlassener Beitrag ohne Begründung ist in der
|
||||
/// Kassenprüfung eine offene Frage.
|
||||
erlassGrund String? @db.VarChar(300)
|
||||
|
||||
bemerkung String? @db.VarChar(500)
|
||||
|
||||
erstelltAm DateTime @default(now()) @db.Timestamptz(3)
|
||||
geaendertAm DateTime @updatedAt @db.Timestamptz(3)
|
||||
|
||||
organisation Organisation @relation(fields: [organisationId], references: [id], onDelete: Cascade)
|
||||
person Person @relation("BeitragPerson", fields: [personId], references: [id], onDelete: Cascade)
|
||||
satz Beitragssatz? @relation(fields: [satzId], references: [id], onDelete: SetNull)
|
||||
buchung Buchung? @relation(fields: [buchungId], references: [id], onDelete: SetNull)
|
||||
|
||||
@@unique([personId, jahr])
|
||||
@@index([organisationId, jahr, stand])
|
||||
@@map("beitrag")
|
||||
}
|
||||
|
||||
/// Eine Rechnung an Dritte.
|
||||
///
|
||||
/// Fehlalarm, Ölspur, Absicherung einer Veranstaltung. Nummer aus dem
|
||||
/// Nummernkreis (`JJJJ-NNNN`), PDF über Gotenberg.
|
||||
model Rechnung {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
organisationId String @db.Uuid
|
||||
|
||||
nummer String @db.VarChar(20)
|
||||
anlass RechnungsAnlass @default(SONSTIGES)
|
||||
stand RechnungStand @default(ENTWURF)
|
||||
|
||||
/// An wen. Bewusst als Text und nicht als Verweis: der Empfänger ist in
|
||||
/// aller Regel niemand, den die Wehr dauerhaft führt.
|
||||
empfaenger String @db.VarChar(200)
|
||||
empfaengerZusatz String? @db.VarChar(200)
|
||||
strasse String? @db.VarChar(200)
|
||||
plz String? @db.VarChar(10)
|
||||
ort String? @db.VarChar(120)
|
||||
|
||||
/// Worauf sie sich bezieht.
|
||||
einsatzId String? @db.Uuid
|
||||
|
||||
datum DateTime @db.Date
|
||||
faelligAm DateTime @db.Date
|
||||
|
||||
/// Summe der Positionen. Wird beim Speichern gerechnet, nicht eingegeben.
|
||||
betrag Decimal @db.Decimal(12, 2)
|
||||
|
||||
bemerkung String? @db.VarChar(1000)
|
||||
|
||||
/// Die Buchung des Zahlungseingangs.
|
||||
buchungId String? @db.Uuid
|
||||
bezahltAm DateTime? @db.Date
|
||||
gemahntAm DateTime? @db.Date
|
||||
mahnstufe Int @default(0)
|
||||
|
||||
pdfDateiId String? @db.Uuid
|
||||
|
||||
erstelltVonId String? @db.Uuid
|
||||
erstelltAm DateTime @default(now()) @db.Timestamptz(3)
|
||||
geaendertAm DateTime @updatedAt @db.Timestamptz(3)
|
||||
|
||||
organisation Organisation @relation(fields: [organisationId], references: [id], onDelete: Cascade)
|
||||
buchung Buchung? @relation(fields: [buchungId], references: [id], onDelete: SetNull)
|
||||
pdfDatei Datei? @relation("RechnungPdf", fields: [pdfDateiId], references: [id], onDelete: SetNull)
|
||||
erstelltVon Person? @relation("RechnungErsteller", fields: [erstelltVonId], references: [id], onDelete: SetNull)
|
||||
positionen Rechnungsposition[]
|
||||
|
||||
@@unique([organisationId, nummer])
|
||||
@@index([organisationId, stand])
|
||||
@@index([organisationId, datum(sort: Desc)])
|
||||
@@map("rechnung")
|
||||
}
|
||||
|
||||
/// Eine Zeile auf einer Rechnung.
|
||||
model Rechnungsposition {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
organisationId String @db.Uuid
|
||||
rechnungId String @db.Uuid
|
||||
|
||||
reihenfolge Int
|
||||
bezeichnung String @db.VarChar(300)
|
||||
/// Menge mit drei Nachkommastellen — Einsatzstunden werden viertelstündlich
|
||||
/// abgerechnet, und 0,25 muss genau sein.
|
||||
menge Decimal @default(1) @db.Decimal(12, 3)
|
||||
einheit String? @db.VarChar(20)
|
||||
einzelpreis Decimal @db.Decimal(12, 2)
|
||||
/// Menge × Einzelpreis, beim Speichern gerechnet.
|
||||
gesamtpreis Decimal @db.Decimal(12, 2)
|
||||
|
||||
erstelltAm DateTime @default(now()) @db.Timestamptz(3)
|
||||
|
||||
organisation Organisation @relation(fields: [organisationId], references: [id], onDelete: Cascade)
|
||||
rechnung Rechnung @relation(fields: [rechnungId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([rechnungId, reihenfolge])
|
||||
@@index([organisationId])
|
||||
@@map("rechnungsposition")
|
||||
}
|
||||
|
||||
/// Ein Kassenabschluss.
|
||||
///
|
||||
/// Der Vorgang, für den es die ganze Kasse gibt: zwei Kassenprüfer setzen sich
|
||||
/// hin, zählen das Bargeld, vergleichen die Kontoauszüge und unterschreiben.
|
||||
/// Danach ist der Zeitraum zu — die Buchungsmaske nimmt kein Datum daraus mehr
|
||||
/// an.
|
||||
///
|
||||
/// Zwei Prüfer, weil es die Satzung fast jeder Wehr so vorsieht. Der zweite
|
||||
/// ist trotzdem optional: eine Wehr, die gerade nur einen hat, soll deshalb
|
||||
/// nicht ohne Abschluss dastehen.
|
||||
model Kassenabschluss {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
organisationId String @db.Uuid
|
||||
|
||||
vonAm DateTime @db.Date
|
||||
bisAm DateTime @db.Date
|
||||
|
||||
/// Summe über alle Konten, aus den Buchungen gerechnet.
|
||||
bestandLautBuch Decimal @db.Decimal(12, 2)
|
||||
/// Was tatsächlich gezählt wurde.
|
||||
gezaehlterBestand Decimal @db.Decimal(12, 2)
|
||||
/// gezaehlt minus laut Buch. Positiv heißt: es war mehr da.
|
||||
differenz Decimal @db.Decimal(12, 2)
|
||||
|
||||
/// Eine Differenz ohne Erklärung ist der Punkt, an dem eine Kassenprüfung
|
||||
/// hängen bleibt.
|
||||
erlaeuterung String? @db.VarChar(2000)
|
||||
|
||||
prueferId String? @db.Uuid
|
||||
zweiterPrueferId String? @db.Uuid
|
||||
geprueftAm DateTime? @db.Date
|
||||
|
||||
berichtDateiId String? @db.Uuid
|
||||
|
||||
/// Festgeschrieben: ab hier ist der Zeitraum zu.
|
||||
festgeschriebenAm DateTime? @db.Timestamptz(3)
|
||||
|
||||
erstelltAm DateTime @default(now()) @db.Timestamptz(3)
|
||||
geaendertAm DateTime @updatedAt @db.Timestamptz(3)
|
||||
|
||||
organisation Organisation @relation(fields: [organisationId], references: [id], onDelete: Cascade)
|
||||
pruefer Person? @relation("AbschlussPruefer", fields: [prueferId], references: [id], onDelete: SetNull)
|
||||
zweiterPruefer Person? @relation("AbschlussZweitpruefer", fields: [zweiterPrueferId], references: [id], onDelete: SetNull)
|
||||
berichtDatei Datei? @relation("AbschlussBericht", fields: [berichtDateiId], references: [id], onDelete: SetNull)
|
||||
posten Abschlussposten[]
|
||||
|
||||
@@index([organisationId, bisAm(sort: Desc)])
|
||||
@@map("kassenabschluss")
|
||||
}
|
||||
|
||||
/// Was auf einem Konto zum Abschluss stand.
|
||||
///
|
||||
/// Je Konto eine Zeile: der Kassenprüfer zählt das Bargeld und liest den
|
||||
/// Kontostand vom Auszug ab. Eine einzelne Summe über alles würde verbergen,
|
||||
/// auf welchem Konto die Differenz sitzt.
|
||||
model Abschlussposten {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
organisationId String @db.Uuid
|
||||
abschlussId String @db.Uuid
|
||||
kontoId String @db.Uuid
|
||||
|
||||
bestandLautBuch Decimal @db.Decimal(12, 2)
|
||||
gezaehlterBestand Decimal @db.Decimal(12, 2)
|
||||
differenz Decimal @db.Decimal(12, 2)
|
||||
|
||||
bemerkung String? @db.VarChar(500)
|
||||
|
||||
erstelltAm DateTime @default(now()) @db.Timestamptz(3)
|
||||
|
||||
organisation Organisation @relation(fields: [organisationId], references: [id], onDelete: Cascade)
|
||||
abschluss Kassenabschluss @relation(fields: [abschlussId], references: [id], onDelete: Cascade)
|
||||
konto Kassenkonto @relation(fields: [kontoId], references: [id], onDelete: Restrict)
|
||||
|
||||
@@unique([abschlussId, kontoId])
|
||||
@@index([organisationId])
|
||||
@@map("abschlussposten")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user