feat(aufgaben): Datenmodell und Regeln für Aufgaben und Projekte
Aufgabe mit Typ, Status, Priorität, Aufwand, Fälligkeit und Herkunft; Projekt als eigene Klammer darum. Zwei Entwurfsentscheidungen: - Die Inbox ist kein eigener Zustand, sondern schlicht eine Aufgabe ohne Bereich. Einsortieren heißt: einen Bereich setzen. Ein zusätzliches Feld könnte mit der Zuordnung auseinanderlaufen. - Projekt ist ein eigenes Modell, kein Aufgabentyp. Ein Projekt hat selbst weder Aufwand noch Fälligkeit, sondern leitet beides aus dem ab, was drinsteckt. ARBEITSPAKET und MEILENSTEIN sind Typen der Aufgaben *innerhalb* eines Projekts. @@unique([quelle, quelleRef]) ist die Grundlage für die Integrationen ab M6: sie upserten darüber, statt zu doppeln. Von Hand erfasste Aufgaben tragen quelleRef = null; PostgreSQL sieht NULL-Werte als verschieden an, sie behindern sich also nicht. Verworfene Aufgaben zählen bewusst gar nicht in den Fortschritt eines Projekts — sonst sähe es fertig aus, weil die Hälfte gestrichen wurde. Fälligkeit wird in Kalendertagen in Europe/Berlin gerechnet, nicht in 24-Stunden-Schritten: was heute um 23:00 fällig ist, ist heute fällig. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
704c490368
commit
63c3c15a3c
+143
-37
@@ -2,8 +2,8 @@
|
||||
// Fachbegriffe auf Deutsch, technische Felder auf Englisch.
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client"
|
||||
output = "../src/generated/prisma"
|
||||
provider = "prisma-client"
|
||||
output = "../src/generated/prisma"
|
||||
moduleFormat = "esm"
|
||||
}
|
||||
|
||||
@@ -18,18 +18,18 @@ enum Rolle {
|
||||
|
||||
/// Ein Mensch, der sich anmelden darf.
|
||||
model Benutzer {
|
||||
id String @id @default(cuid())
|
||||
email String @unique
|
||||
name String?
|
||||
id String @id @default(cuid())
|
||||
email String @unique
|
||||
name String?
|
||||
/// Nur gesetzt, wenn die Anmeldung per Passwort erlaubt ist.
|
||||
passwortHash String?
|
||||
rolle Rolle @default(BENUTZER)
|
||||
aktiv Boolean @default(true)
|
||||
bildUrl String?
|
||||
emailGeprueft DateTime?
|
||||
zuletztAktiv DateTime?
|
||||
erstelltAm DateTime @default(now())
|
||||
aktualisiertAm DateTime @updatedAt
|
||||
passwortHash String?
|
||||
rolle Rolle @default(BENUTZER)
|
||||
aktiv Boolean @default(true)
|
||||
bildUrl String?
|
||||
emailGeprueft DateTime?
|
||||
zuletztAktiv DateTime?
|
||||
erstelltAm DateTime @default(now())
|
||||
aktualisiertAm DateTime @updatedAt
|
||||
|
||||
konten Konto[]
|
||||
passkeys Passkey[]
|
||||
@@ -63,19 +63,19 @@ model Konto {
|
||||
|
||||
/// Ein registrierter Passkey (WebAuthn) eines Benutzers.
|
||||
model Passkey {
|
||||
id String @id @default(cuid())
|
||||
benutzerId String
|
||||
id String @id @default(cuid())
|
||||
benutzerId String
|
||||
/// Base64URL der Credential-ID.
|
||||
credentialId String @unique
|
||||
publicKey Bytes
|
||||
counter BigInt @default(0)
|
||||
credentialId String @unique
|
||||
publicKey Bytes
|
||||
counter BigInt @default(0)
|
||||
/// Kommagetrennt, z. B. "internal,hybrid".
|
||||
transports String?
|
||||
geraetetyp String?
|
||||
gesichert Boolean @default(false)
|
||||
bezeichnung String
|
||||
erstelltAm DateTime @default(now())
|
||||
zuletztBenutzt DateTime?
|
||||
transports String?
|
||||
geraetetyp String?
|
||||
gesichert Boolean @default(false)
|
||||
bezeichnung String
|
||||
erstelltAm DateTime @default(now())
|
||||
zuletztBenutzt DateTime?
|
||||
|
||||
benutzer Benutzer @relation(fields: [benutzerId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@ -98,12 +98,12 @@ model WebauthnChallenge {
|
||||
|
||||
/// Lebensbereich mit Farbe, Tageskapazitaet und Arbeitsfenster.
|
||||
model Bereich {
|
||||
id String @id @default(cuid())
|
||||
name String @unique
|
||||
id String @id @default(cuid())
|
||||
name String @unique
|
||||
/// Hex-Farbe inklusive Raute, z. B. "#2563eb".
|
||||
farbe String
|
||||
reihenfolge Int @default(0)
|
||||
aktiv Boolean @default(true)
|
||||
farbe String
|
||||
reihenfolge Int @default(0)
|
||||
aktiv Boolean @default(true)
|
||||
|
||||
/// Tageskapazitaet in Minuten, Montag bis Sonntag.
|
||||
kapazitaetMo Int @default(0)
|
||||
@@ -121,23 +121,129 @@ model Bereich {
|
||||
erstelltAm DateTime @default(now())
|
||||
aktualisiertAm DateTime @updatedAt
|
||||
|
||||
aufgaben Aufgabe[]
|
||||
projekte Projekt[]
|
||||
|
||||
@@index([aktiv, reihenfolge])
|
||||
@@map("bereich")
|
||||
}
|
||||
|
||||
enum AufgabenTyp {
|
||||
AUFGABE
|
||||
IDEE
|
||||
ARBEITSPAKET
|
||||
MEILENSTEIN
|
||||
SPIEGEL
|
||||
}
|
||||
|
||||
enum AufgabenStatus {
|
||||
OFFEN
|
||||
GEPLANT
|
||||
WARTET
|
||||
ERLEDIGT
|
||||
VERWORFEN
|
||||
}
|
||||
|
||||
/// Woher eine Aufgabe stammt. Integrationen upserten ueber [quelle, quelleRef].
|
||||
enum Quelle {
|
||||
MANUELL
|
||||
ZAMMAD
|
||||
WARTUNGSPLANER
|
||||
TELEGRAM
|
||||
SPIEGEL
|
||||
MAIL
|
||||
API
|
||||
MCP
|
||||
}
|
||||
|
||||
enum ProjektStatus {
|
||||
AKTIV
|
||||
PAUSIERT
|
||||
ABGESCHLOSSEN
|
||||
VERWORFEN
|
||||
}
|
||||
|
||||
/// Klammer um mehrere Aufgaben. Hat selbst keinen Aufwand und keine
|
||||
/// Faelligkeit – beides ergibt sich aus den enthaltenen Aufgaben.
|
||||
model Projekt {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
beschreibung String?
|
||||
status ProjektStatus @default(AKTIV)
|
||||
bereichId String?
|
||||
/// Wunschtermin fuer den Abschluss, rein informativ.
|
||||
zielAm DateTime?
|
||||
|
||||
erstelltAm DateTime @default(now())
|
||||
aktualisiertAm DateTime @updatedAt
|
||||
|
||||
bereich Bereich? @relation(fields: [bereichId], references: [id], onDelete: SetNull)
|
||||
aufgaben Aufgabe[]
|
||||
|
||||
@@unique([name])
|
||||
@@index([status])
|
||||
@@index([bereichId])
|
||||
@@map("projekt")
|
||||
}
|
||||
|
||||
/// Eine Aufgabe, Idee, ein Arbeitspaket oder ein Meilenstein.
|
||||
///
|
||||
/// Ohne Bereich liegt sie in der Inbox – das ist der einzige Unterschied,
|
||||
/// "einsortieren" heisst schlicht: einen Bereich setzen.
|
||||
model Aufgabe {
|
||||
id String @id @default(cuid())
|
||||
titel String
|
||||
notiz String?
|
||||
typ AufgabenTyp @default(AUFGABE)
|
||||
status AufgabenStatus @default(OFFEN)
|
||||
|
||||
/// 1 = dringend, 5 = irgendwann. 3 ist der Normalfall.
|
||||
prioritaet Int @default(3)
|
||||
/// Geschaetzter Aufwand in Minuten; null heisst "noch nicht geschaetzt".
|
||||
aufwandMinuten Int?
|
||||
|
||||
faelligAm DateTime?
|
||||
erledigtAm DateTime?
|
||||
|
||||
bereichId String?
|
||||
projektId String?
|
||||
|
||||
/// Herkunft. Fuer von Hand erfasste Aufgaben MANUELL ohne Verweis.
|
||||
quelle Quelle @default(MANUELL)
|
||||
quelleUrl String?
|
||||
/// Kennung beim Ursprungssystem, z. B. die Ticketnummer.
|
||||
quelleRef String?
|
||||
|
||||
erstelltAm DateTime @default(now())
|
||||
aktualisiertAm DateTime @updatedAt
|
||||
|
||||
bereich Bereich? @relation(fields: [bereichId], references: [id], onDelete: SetNull)
|
||||
projekt Projekt? @relation(fields: [projektId], references: [id], onDelete: SetNull)
|
||||
|
||||
/// Integrationen upserten immer ueber diese beiden Felder. Von Hand
|
||||
/// erfasste Aufgaben haben quelleRef = null; PostgreSQL sieht NULL-Werte
|
||||
/// als verschieden an, sie behindern sich also nicht.
|
||||
@@unique([quelle, quelleRef])
|
||||
@@index([status, faelligAm])
|
||||
@@index([bereichId, status])
|
||||
@@index([projektId])
|
||||
@@index([typ, status])
|
||||
@@map("aufgabe")
|
||||
}
|
||||
|
||||
/// Unveraenderlicher Nachweis jeder schreibenden Aktion.
|
||||
model AuditEintrag {
|
||||
id String @id @default(cuid())
|
||||
zeitpunkt DateTime @default(now())
|
||||
benutzerId String?
|
||||
id String @id @default(cuid())
|
||||
zeitpunkt DateTime @default(now())
|
||||
benutzerId String?
|
||||
/// Bleibt erhalten, auch wenn der Benutzer spaeter geloescht wird.
|
||||
benutzerEmail String
|
||||
aktion String
|
||||
objektTyp String
|
||||
objektId String?
|
||||
aktion String
|
||||
objektTyp String
|
||||
objektId String?
|
||||
/// Nur fachliche Felder, niemals Tokens, Mailinhalte oder Termin-Betreffs.
|
||||
details Json?
|
||||
instanz String
|
||||
details Json?
|
||||
instanz String
|
||||
|
||||
benutzer Benutzer? @relation(fields: [benutzerId], references: [id], onDelete: SetNull)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user