feat: add household emergency duty alarms and deadline worker

This commit is contained in:
t.indorf
2026-10-09 11:02:24 +02:00
parent a96ba0fd84
commit 8b32e4a832
34 changed files with 3947 additions and 646 deletions
+127 -6
View File
@@ -84,6 +84,15 @@ enum NotdienstAlertStatus {
CANCELLED
}
enum NotdienstAlarmStatus {
PENDING
CONFIRMED
DECLINED
LATE_CONFIRMATION
REVIEW_REQUIRED
CANCELLED
}
enum NotdienstSwapStatus {
PENDING
ACCEPTED
@@ -143,6 +152,7 @@ model Kita {
notdienstAvailabilities NotdienstAvailability[]
notdienstAssignments NotdienstAssignment[]
notdienstAlerts NotdienstAlert[]
notdienstAlarms NotdienstAlarm[]
notdienstSwapRequests NotdienstSwapRequest[]
staffProfiles StaffProfile[]
staffHolidays StaffHoliday[]
@@ -160,7 +170,8 @@ model Family {
kitaId String
kita Kita @relation(fields: [kitaId], references: [id], onDelete: Cascade)
name String
name String
notdienstAlarms NotdienstAlarm[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@ -240,11 +251,14 @@ model User {
termineApproved Termin[] @relation("TerminApprover")
mitbringselItems MitbringselItem[]
invitationsCreated Invitation[] @relation("InvitationCreator")
auditLogsAuthored AuditLog[] @relation("AuditLogActor")
invitationsCreated Invitation[] @relation("InvitationCreator")
auditLogsAuthored AuditLog[] @relation("AuditLogActor")
staffProfile StaffProfile?
staffNotifications StaffNotification[]
staffEmails StaffEmail[]
alarmRecipients NotdienstAlarmRecipient[]
alarmNotifications NotdienstAlarmNotification[]
alarmEmails NotdienstAlarmEmail[]
@@index([kitaId])
@@index([familyId])
@@ -890,6 +904,7 @@ model NotdienstAssignment {
updatedAt DateTime @updatedAt
alerts NotdienstAlert[]
alarms NotdienstAlarm[]
swapRequestsAsTarget NotdienstSwapRequest[] @relation("NotdienstSwapTargetAssignment")
swapRequestsAsOffered NotdienstSwapRequest[] @relation("NotdienstSwapOfferedAssignment")
@@ -936,10 +951,116 @@ model NotdienstSwapRequest {
/// Aktive Alarmierung — wird bei Krankmeldung einer Fachkraft
/// vom Koordinator ausgelöst. Bestätigung via Magic-Link (Token).
model NotdienstAlarm {
id String @id @default(cuid())
kitaId String
kita Kita @relation(fields: [kitaId], references: [id], onDelete: Cascade)
assignmentId String?
assignment NotdienstAssignment? @relation(fields: [assignmentId], references: [id], onDelete: SetNull)
// Nullable unique reservation: cancelled or ended alarms release the assignment.
activeAssignmentId String? @unique
familyId String
family Family @relation(fields: [familyId], references: [id], onDelete: Cascade)
familyName String
childName String
date DateTime @db.Date
wholeDay Boolean @default(true)
startMinute Int?
endMinute Int?
expiresAt DateTime
deadline DateTime
status NotdienstAlarmStatus @default(PENDING)
triggeredById String?
triggeredAt DateTime @default(now())
respondedById String?
respondedAt DateTime?
responseSource String?
escalatedAt DateTime?
educatorId String?
notes String?
cancelledAt DateTime?
cancelReason String?
recipients NotdienstAlarmRecipient[]
notifications NotdienstAlarmNotification[]
emails NotdienstAlarmEmail[]
history NotdienstAlarmHistory[]
legacyAlerts NotdienstAlert[]
@@index([kitaId, date])
@@index([status, deadline])
@@map("notdienst_alarms")
}
model NotdienstAlarmRecipient {
id String @id @default(cuid())
alarmId String
alarm NotdienstAlarm @relation(fields: [alarmId], references: [id], onDelete: Cascade)
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
token String @unique
@@unique([alarmId, userId])
@@map("notdienst_alarm_recipients")
}
model NotdienstAlarmNotification {
id String @id @default(cuid())
alarmId String
alarm NotdienstAlarm @relation(fields: [alarmId], references: [id], onDelete: Cascade)
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
eventKey String
message String
createdAt DateTime @default(now())
readAt DateTime?
@@unique([alarmId, userId, eventKey])
@@index([userId, readAt])
@@map("notdienst_alarm_notifications")
}
model NotdienstAlarmEmail {
id String @id @default(cuid())
alarmId String
alarm NotdienstAlarm @relation(fields: [alarmId], references: [id], onDelete: Cascade)
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
eventKey String
kind String
subject String
message String
path String
createdAt DateTime @default(now())
sentAt DateTime?
skippedAt DateTime?
leaseUntil DateTime?
failed Boolean @default(false)
attempts Int @default(0)
@@unique([alarmId, userId, eventKey])
@@index([sentAt, skippedAt, createdAt])
@@map("notdienst_alarm_emails")
}
model NotdienstAlarmHistory {
id String @id @default(cuid())
alarmId String
alarm NotdienstAlarm @relation(fields: [alarmId], references: [id], onDelete: Cascade)
actorId String?
action String
details String
createdAt DateTime @default(now())
@@index([alarmId, createdAt])
@@map("notdienst_alarm_history")
}
model NotdienstAlert {
id String @id @default(cuid())
kitaId String
kita Kita @relation(fields: [kitaId], references: [id], onDelete: Cascade)
id String @id @default(cuid())
alarmId String?
alarm NotdienstAlarm? @relation(fields: [alarmId], references: [id], onDelete: SetNull)
kitaId String
kita Kita @relation(fields: [kitaId], references: [id], onDelete: Cascade)
assignmentId String
assignment NotdienstAssignment @relation(fields: [assignmentId], references: [id], onDelete: Cascade)