Files
schulos/prisma/schema.prisma
T

1591 lines
59 KiB
Plaintext

// CompetenceTrack — Full Database Schema
// SQLite via Prisma ORM | UUIDs for portability | UTC timestamps
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
// ─── Core Organizational Entities ───────────────────────────────────
model School {
id String @id @default(cuid())
name String
schoolType String // ELEMENTARY, MIDDLE, GYMNASIUM, OTHER
country String @default("DE")
timezone String @default("Europe/Berlin")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// School branding fields
logoUrl String? // school logo URL
primaryColor String? @default("#10b981") // primary brand color
secondaryColor String? @default("#14b8a6") // secondary brand color
accentColor String? @default("#059669") // accent brand color
fontFamily String? @default("Inter") // preferred font
customCss String? // custom CSS overrides
motto String? // school motto
websiteUrl String? // school website URL
emailDomain String? // school email domain (e.g., "@schule.de")
address String? // school address
phone String? // school phone number
users User[]
schoolYears SchoolYear[]
classGroups ClassGroup[]
students Student[]
subjects Subject[]
competencyTemplates CompetencyTemplate[]
gradingSchemes GradingScheme[]
auditLogs AuditLog[]
dataExportRequests DataExportRequest[]
behaviorCategories BehaviorCategory[]
behaviorIncidents BehaviorIncident[]
rubrics Rubric[]
commentCategories CommentCategory[]
commentBankEntries CommentBankEntry[]
notebooks Notebook[]
drawings Drawing[]
notifications Notification[]
calendarEvents CalendarEvent[]
parentStudentLinks ParentStudentLink[]
curriculumStandards CurriculumStandard[]
backups Backup[]
behaviorInterventions BehaviorIntervention[]
reportSchedules ReportSchedule[]
homeworks Homework[]
announcements Announcement[]
selfAssessments SelfAssessment[]
learningGoals LearningGoal[]
portfolioEntries PortfolioEntry[]
timetableSlots TimetableSlot[]
resources Resource[]
emailTemplates EmailTemplate[]
emailLogs EmailLog[]
peerAssessments PeerAssessment[]
emergencyContacts EmergencyContact[]
schoolEvents SchoolEvent[]
studentTransports StudentTransport[]
healthRecords HealthRecord[]
badges Badge[]
studentBadges StudentBadge[]
district SchoolDistrict? @relation(fields: [districtId], references: [id])
districtId String?
}
model User {
id String @id @default(cuid())
schoolId String?
email String @unique
passwordHash String
firstName String
lastName String
role String @default("TEACHER") // TEACHER, SCHOOL_ADMIN, SUPER_ADMIN, STUDENT, PARENT
locale String @default("de")
twoFactorSecret String?
isDemo Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
school School? @relation(fields: [schoolId], references: [id])
classGroupTeachers ClassGroupTeacher[]
learningProgressEntries LearningProgressEntry[]
assessments Assessment[]
generatedReports Report[]
auditLogs AuditLog[]
dataExportRequests DataExportRequest[]
teacherNotes TeacherNote[]
attendanceSessions AttendanceSession[]
lessonPlans LessonPlan[]
parentMessagesSent ParentMessage[]
behaviorIncidentsLogged BehaviorIncident[] @relation("BehaviorLogger")
behaviorIncidentsResolved BehaviorIncident[] @relation("BehaviorResolver")
rubrics Rubric[]
commentBankEntries CommentBankEntry[]
notebooks Notebook[]
drawings Drawing[]
notifications Notification[]
calendarEvents CalendarEvent[]
parentLinks ParentStudentLink[]
assignedInterventions BehaviorIntervention[] @relation("InterventionAssignee")
homeworks Homework[]
announcements Announcement[]
timetableSlots TimetableSlot[]
resources Resource[]
organizedSchoolEvents SchoolEvent[]
eventRegistrations EventRegistration[]
awardedBadges StudentBadge[]
}
model SchoolYear {
id String @id @default(cuid())
schoolId String
label String // e.g. "2025/2026"
startDate DateTime
endDate DateTime
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
school School @relation(fields: [schoolId], references: [id])
classGroups ClassGroup[]
enrollments Enrollment[]
computedGrades ComputedGrade[]
reports Report[]
}
model ClassGroup {
id String @id @default(cuid())
schoolId String
schoolYearId String
name String // e.g. "3a"
gradeLevel Int
schoolType String @default("ELEMENTARY")
seatingOrder String? // JSON array of student IDs
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
school School @relation(fields: [schoolId], references: [id])
schoolYear SchoolYear @relation(fields: [schoolYearId], references: [id])
teachers ClassGroupTeacher[]
enrollments Enrollment[]
classCompetencyAssignments ClassCompetencyAssignment[]
assessments Assessment[]
learningProgressEntries LearningProgressEntry[]
gradingSchemes GradingScheme[]
computedGrades ComputedGrade[]
reports Report[]
attendanceSessions AttendanceSession[]
lessonPlans LessonPlan[]
behaviorIncidents BehaviorIncident[]
notebooks Notebook[]
drawings Drawing[]
calendarEvents CalendarEvent[]
reportSchedules ReportSchedule[]
homeworks Homework[]
announcements Announcement[]
selfAssessments SelfAssessment[]
timetableSlots TimetableSlot[]
resources Resource[]
peerAssessments PeerAssessment[]
schoolEvents SchoolEvent[]
}
model ClassGroupTeacher {
id String @id @default(cuid())
classGroupId String
userId String
role String @default("SUBJECT_TEACHER") // HOMEROOM_TEACHER, SUBJECT_TEACHER
classGroup ClassGroup @relation(fields: [classGroupId], references: [id])
user User @relation(fields: [userId], references: [id])
@@unique([classGroupId, userId])
}
model Student {
id String @id @default(cuid())
schoolId String
firstName String
lastName String
dateOfBirth DateTime?
externalId String? // for import matching
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
school School @relation(fields: [schoolId], references: [id])
enrollments Enrollment[]
learningProgressEntries LearningProgressEntry[]
assessmentResults AssessmentResult[]
computedGrades ComputedGrade[]
reports Report[]
teacherNotes TeacherNote[]
attendanceRecords AttendanceRecord[]
parentContacts ParentContact[]
parentMessages ParentMessage[]
behaviorIncidents BehaviorIncident[]
behaviorInterventions BehaviorIntervention[]
parentStudentLinks ParentStudentLink[]
homeworkSubmissions HomeworkSubmission[]
selfAssessments SelfAssessment[]
learningGoals LearningGoal[]
portfolioEntries PortfolioEntry[]
peerAssessmentsGiven PeerAssessment[] @relation("PeerAssessor")
peerAssessmentsReceived PeerAssessment[] @relation("PeerAssessed")
emergencyContacts EmergencyContact[]
transports StudentTransport[]
healthRecords HealthRecord[]
studentBadges StudentBadge[]
}
model Enrollment {
id String @id @default(cuid())
studentId String
classGroupId String
schoolYearId String
startDate DateTime
endDate DateTime? // null = still enrolled
student Student @relation(fields: [studentId], references: [id])
classGroup ClassGroup @relation(fields: [classGroupId], references: [id])
schoolYear SchoolYear @relation(fields: [schoolYearId], references: [id])
}
// ─── Subject & Competency Structure ────────────────────────────────
model Subject {
id String @id @default(cuid())
schoolId String? // null = global/template
name String
gradeLevelMin Int @default(1)
gradeLevelMax Int @default(13)
school School? @relation(fields: [schoolId], references: [id])
competencyTemplates CompetencyTemplate[]
assessments Assessment[]
classCompetencyAssignments ClassCompetencyAssignment[]
gradingSchemes GradingScheme[]
computedGrades ComputedGrade[]
attendanceSessions AttendanceSession[]
lessonPlans LessonPlan[]
rubrics Rubric[]
commentBankEntries CommentBankEntry[]
notebooks Notebook[]
drawings Drawing[]
calendarEvents CalendarEvent[]
curriculumStandards CurriculumStandard[]
homeworks Homework[]
timetableSlots TimetableSlot[]
resources Resource[]
}
model CompetencyTemplate {
id String @id @default(cuid())
name String
description String?
subjectId String?
schoolType String @default("ELEMENTARY")
gradeLevelMin Int @default(1)
gradeLevelMax Int @default(4)
isGlobalTemplate Boolean @default(false)
createdByUserId String?
version Int @default(1)
schoolId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
subject Subject? @relation(fields: [subjectId], references: [id])
school School? @relation(fields: [schoolId], references: [id])
categories CompetencyCategory[]
classCompetencyAssignments ClassCompetencyAssignment[]
}
model CompetencyCategory {
id String @id @default(cuid())
competencyTemplateId String
name String
order Int @default(0)
color String? // hex color for chart petals
competencyTemplate CompetencyTemplate @relation(fields: [competencyTemplateId], references: [id])
competencies Competency[]
reportSections ReportSection[]
}
model Competency {
id String @id @default(cuid())
categoryId String
code String // e.g. "D.3.LES.01"
title String
description String?
order Int @default(0)
category CompetencyCategory @relation(fields: [categoryId], references: [id])
masteryLevelDefinitions MasteryLevelDefinition[]
learningProgressEntries LearningProgressEntry[]
assessmentCompetencyLinks AssessmentCompetencyLink[]
curriculumStandardLinks CurriculumStandardLink[]
selfAssessments SelfAssessment[]
learningGoals LearningGoal[]
portfolioEntries PortfolioEntry[]
peerAssessments PeerAssessment[]
}
model MasteryLevelDefinition {
id String @id @default(cuid())
competencyId String
levelValue Int // e.g. 1-4
label String // e.g. "noch nicht erreicht", "entwickelt sich", "kompetent", "weit entwickelt"
description String?
competency Competency @relation(fields: [competencyId], references: [id])
}
model ClassCompetencyAssignment {
id String @id @default(cuid())
classGroupId String
subjectId String
competencyTemplateId String
schoolYearId String
clonedTemplateId String? // if customized, points to the school's cloned copy
classGroup ClassGroup @relation(fields: [classGroupId], references: [id])
subject Subject @relation(fields: [subjectId], references: [id])
competencyTemplate CompetencyTemplate @relation(fields: [competencyTemplateId], references: [id])
@@unique([classGroupId, subjectId, schoolYearId])
}
// ─── Progress Tracking & Assessment ────────────────────────────────
model LearningProgressEntry {
id String @id @default(cuid())
studentId String
competencyId String
teacherId String
classGroupId String
date DateTime
masteryLevelValue Int
note String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
student Student @relation(fields: [studentId], references: [id])
competency Competency @relation(fields: [competencyId], references: [id])
teacher User @relation(fields: [teacherId], references: [id])
classGroup ClassGroup @relation(fields: [classGroupId], references: [id])
}
model Assessment {
id String @id @default(cuid())
classGroupId String
subjectId String
teacherId String
title String
date DateTime
type String @default("TEST") // TEST, ORAL, PROJECT, HOMEWORK, OTHER
maxScore Float?
weight Float @default(1.0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
classGroup ClassGroup @relation(fields: [classGroupId], references: [id])
subject Subject @relation(fields: [subjectId], references: [id])
teacher User @relation(fields: [teacherId], references: [id])
assessmentCompetencyLinks AssessmentCompetencyLink[]
assessmentResults AssessmentResult[]
}
model AssessmentCompetencyLink {
id String @id @default(cuid())
assessmentId String
competencyId String
weight Float @default(1.0)
assessment Assessment @relation(fields: [assessmentId], references: [id])
competency Competency @relation(fields: [competencyId], references: [id])
@@unique([assessmentId, competencyId])
}
model AssessmentResult {
id String @id @default(cuid())
assessmentId String
studentId String
score Float?
masteryLevelValue Int?
note String?
assessment Assessment @relation(fields: [assessmentId], references: [id])
student Student @relation(fields: [studentId], references: [id])
@@unique([assessmentId, studentId])
}
// ─── Grading ───────────────────────────────────────────────────────
model GradingScheme {
id String @id @default(cuid())
classGroupId String?
subjectId String?
schoolId String?
name String
type String @default("NUMERIC_GRADE") // NUMERIC_GRADE, VERBAL_FEEDBACK, COMBINED
scaleDefinition String // JSON: e.g. {"min":1,"max":6,"labels":{"1":"sehr gut","6":"ungenügend"}}
classGroup ClassGroup? @relation(fields: [classGroupId], references: [id])
subject Subject? @relation(fields: [subjectId], references: [id])
school School? @relation(fields: [schoolId], references: [id])
gradingWeightRules GradingWeightRule[]
}
model GradingWeightRule {
id String @id @default(cuid())
gradingSchemeId String
sourceType String // LEARNING_PROGRESS, ASSESSMENT
targetRef String? // competencyId, assessmentId, or categoryId
weightPercent Float @default(50.0)
gradingScheme GradingScheme @relation(fields: [gradingSchemeId], references: [id])
}
model ComputedGrade {
id String @id @default(cuid())
studentId String
subjectId String
classGroupId String
schoolYearId String
period String @default("Semester 1")
computedValue Float
computedAt DateTime @default(now())
isFinalized Boolean @default(false)
overriddenValue Float?
overrideReason String?
student Student @relation(fields: [studentId], references: [id])
subject Subject @relation(fields: [subjectId], references: [id])
classGroup ClassGroup @relation(fields: [classGroupId], references: [id])
schoolYear SchoolYear @relation(fields: [schoolYearId], references: [id])
}
// ─── Reporting ─────────────────────────────────────────────────────
model Report {
id String @id @default(cuid())
studentId String
classGroupId String
schoolYearId String
period String
generatedByUserId String
generatedAt DateTime @default(now())
status String @default("DRAFT") // DRAFT, FINAL
pdfFilePath String?
includesGrades Boolean @default(false)
student Student @relation(fields: [studentId], references: [id])
classGroup ClassGroup @relation(fields: [classGroupId], references: [id])
schoolYear SchoolYear @relation(fields: [schoolYearId], references: [id])
generatedByUser User @relation(fields: [generatedByUserId], references: [id])
sections ReportSection[]
}
model ReportSection {
id String @id @default(cuid())
reportId String
competencyCategoryId String?
generatedText String
order Int @default(0)
report Report @relation(fields: [reportId], references: [id])
competencyCategory CompetencyCategory? @relation(fields: [competencyCategoryId], references: [id])
}
// ─── Auditing & Compliance ─────────────────────────────────────────
model AuditLog {
id String @id @default(cuid())
userId String?
schoolId String?
action String
entityType String
entityId String?
changes String? // JSON: { before: {...}, after: {...} }
ipAddress String?
userAgent String?
timestamp DateTime @default(now())
metadata String? // JSON
user User? @relation(fields: [userId], references: [id])
school School? @relation(fields: [schoolId], references: [id])
}
model DataExportRequest {
id String @id @default(cuid())
requestedByUserId String
schoolId String?
scope String // STUDENT, CLASS, SCHOOL
scopeId String? // studentId, classGroupId, or schoolId
status String @default("PENDING") // PENDING, PROCESSING, COMPLETED, FAILED
filePath String?
requestedAt DateTime @default(now())
completedAt DateTime?
requestedByUser User @relation(fields: [requestedByUserId], references: [id])
school School? @relation(fields: [schoolId], references: [id])
}
// ─── Attendance Tracking ────────────────────────────────────────────
model AttendanceSession {
id String @id @default(cuid())
classGroupId String
teacherId String
date DateTime
subjectId String?
period String? // e.g. "1. Stunde", "Morning"
status String @default("OPEN") // OPEN, COMPLETED
note String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
classGroup ClassGroup @relation(fields: [classGroupId], references: [id])
teacher User @relation(fields: [teacherId], references: [id])
subject Subject? @relation(fields: [subjectId], references: [id])
records AttendanceRecord[]
@@index([classGroupId, date])
}
model AttendanceRecord {
id String @id @default(cuid())
sessionId String
studentId String
status String @default("PRESENT") // PRESENT, ABSENT, EXCUSED, LATE
arrivalTime String? // for LATE status, e.g. "08:25"
comment String?
session AttendanceSession @relation(fields: [sessionId], references: [id])
student Student @relation(fields: [studentId], references: [id])
@@unique([sessionId, studentId])
@@index([studentId])
}
// ─── Teacher Notes ─────────────────────────────────────────────────
model TeacherNote {
id String @id @default(cuid())
studentId String
teacherId String
category String // GENERAL, BEHAVIOR, ACADEMIC, INTERVENTION, PARENT_CONTACT
content String
isPrivate Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
student Student @relation(fields: [studentId], references: [id])
teacher User @relation(fields: [teacherId], references: [id])
@@index([studentId])
@@index([teacherId])
}
// ─── Lesson Planning ────────────────────────────────────────────────
model LessonPlan {
id String @id @default(cuid())
teacherId String
classGroupId String
subjectId String?
title String
description String?
date DateTime
durationMin Int @default(45)
status String @default("draft") // draft|scheduled|completed|cancelled
objectives String? // JSON array of strings
materials String? // JSON array of strings
homework String?
reflection String?
linkedCompetencyIds String? // JSON array of competency IDs
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
teacher User @relation(fields: [teacherId], references: [id])
classGroup ClassGroup @relation(fields: [classGroupId], references: [id])
subject Subject? @relation(fields: [subjectId], references: [id])
@@index([teacherId])
@@index([classGroupId])
@@index([date])
}
// ─── Parent Communication ──────────────────────────────────────────
model ParentContact {
id String @id @default(cuid())
studentId String
firstName String
lastName String
email String?
phone String?
relationship String @default("parent") // parent|guardian|emergency
preferredContact String @default("email") // email|phone|both
preferredLanguage String @default("de")
notes String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
student Student @relation(fields: [studentId], references: [id])
messages ParentMessage[]
@@index([studentId])
@@unique([studentId, email])
}
model ParentMessage {
id String @id @default(cuid())
parentId String
teacherId String
studentId String
subject String
body String
category String @default("general") // general|progress|assessment|behavior|attendance|event
priority String @default("normal") // low|normal|high|urgent
status String @default("sent") // draft|sent|delivered|read|replied
readAt DateTime?
reply String?
replyAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
parent ParentContact @relation(fields: [parentId], references: [id])
teacher User @relation(fields: [teacherId], references: [id])
student Student @relation(fields: [studentId], references: [id])
@@index([teacherId])
@@index([parentId])
@@index([studentId])
@@index([createdAt])
}
// ─── Behavior Tracking ─────────────────────────────────────────────
model BehaviorCategory {
id String @id @default(cuid())
schoolId String
school School @relation(fields: [schoolId], references: [id])
name String
color String // hex color, e.g. "#10b981"
valence String // "positive" | "negative" | "neutral"
icon String? // emoji
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
incidents BehaviorIncident[]
@@unique([schoolId, name])
@@index([schoolId])
}
model BehaviorIncident {
id String @id @default(cuid())
studentId String
student Student @relation(fields: [studentId], references: [id])
teacherId String
teacher User @relation("BehaviorLogger", fields: [teacherId], references: [id])
classGroupId String?
classGroup ClassGroup? @relation(fields: [classGroupId], references: [id])
schoolId String
school School @relation(fields: [schoolId], references: [id])
categoryId String
category BehaviorCategory @relation(fields: [categoryId], references: [id])
date DateTime
severity String // "minor" | "moderate" | "major"
description String
location String? // e.g., "Klassenzimmer", "Pausenhof", "Flur"
followUpAction String? // e.g., "Elterngespräch", "Nachsitzen", "Lob im Kreis"
resolved Boolean @default(false)
resolvedAt DateTime?
resolvedById String?
resolvedBy User? @relation("BehaviorResolver", fields: [resolvedById], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([studentId, date])
@@index([classGroupId, date])
@@index([schoolId, date])
@@index([teacherId])
interventions BehaviorIntervention[]
}
// ─── Rubric Library ────────────────────────────────────────────────
model Rubric {
id String @id @default(cuid())
schoolId String
school School @relation(fields: [schoolId], references: [id])
teacherId String
teacher User @relation(fields: [teacherId], references: [id])
subjectId String?
subject Subject? @relation(fields: [subjectId], references: [id])
title String
description String?
type String @default("ANALYTIC") // ANALYTIC | HOLISTIC
maxPoints Int @default(100)
isPublic Boolean @default(false) // shared across school
version Int @default(1)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
criteria RubricCriterion[]
peerAssessments PeerAssessment[]
@@index([schoolId])
@@index([teacherId])
@@index([subjectId])
}
model RubricCriterion {
id String @id @default(cuid())
rubricId String
rubric Rubric @relation(fields: [rubricId], references: [id], onDelete: Cascade)
name String // e.g., "Inhalt", "Sprache", "Darstellung"
description String?
weight Float @default(1.0) // relative weight for this criterion
maxPoints Int // max points for this criterion
order Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
levels RubricLevel[]
@@index([rubricId])
}
model RubricLevel {
id String @id @default(cuid())
criterionId String
criterion RubricCriterion @relation(fields: [criterionId], references: [id], onDelete: Cascade)
label String // e.g., "Sehr gut", "Gut", "Befriedigend", "Ausreichend"
description String // descriptor for this level
points Int // points for this level
order Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([criterionId])
}
// ─── Report Card Comments Bank ────────────────────────────────────
model CommentCategory {
id String @id @default(cuid())
schoolId String
school School @relation(fields: [schoolId], references: [id])
name String
color String // hex color
icon String? // emoji
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
comments CommentBankEntry[]
@@unique([schoolId, name])
@@index([schoolId])
}
model CommentBankEntry {
id String @id @default(cuid())
schoolId String
school School @relation(fields: [schoolId], references: [id])
teacherId String
teacher User @relation(fields: [teacherId], references: [id])
categoryId String
category CommentCategory @relation(fields: [categoryId], references: [id])
subjectId String?
subject Subject? @relation(fields: [subjectId], references: [id])
title String // short title
text String // the full comment text
gradeLevel String? // e.g., "3", "3-4", "all" — which grade levels this applies to
schoolType String? // e.g., "ELEMENTARY", "SECONDARY"
isPublic Boolean @default(false) // shared across school
usageCount Int @default(0) // how many times used
tags String? // comma-separated tags for search
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([schoolId])
@@index([teacherId])
@@index([categoryId])
@@index([subjectId])
}
// ─── Digital Notebooks ─────────────────────────────────────────────
model Notebook {
id String @id @default(cuid())
schoolId String
ownerId String // userId of the student or teacher
ownerType String // STUDENT | TEACHER
subjectId String? // null = personal/general notebook
classGroupId String?
title String
description String?
notebookType String @default("lined") // lined | grid | blank | dotted | music | calligraphy
color String @default("#10b981") // cover color
icon String? // lucide icon name
isArchived Boolean @default(false)
isPublic Boolean @default(false) // shared with class
sortOrder Int @default(0)
isDemo Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
school School @relation(fields: [schoolId], references: [id])
owner User @relation(fields: [ownerId], references: [id])
subject Subject? @relation(fields: [subjectId], references: [id])
classGroup ClassGroup? @relation(fields: [classGroupId], references: [id])
pages NotebookPage[]
@@index([schoolId])
@@index([ownerId])
@@index([subjectId])
@@index([classGroupId])
@@index([isArchived])
}
model NotebookPage {
id String @id @default(cuid())
notebookId String
pageNumber Int
title String?
contentType String @default("text") // text | drawing | mixed
textContent String? // markdown/rich text content
drawingData String? // JSON with strokes/paths for canvas
background String @default("lined") // lined | grid | blank | dotted | music
isBookmark Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
notebook Notebook @relation(fields: [notebookId], references: [id], onDelete: Cascade)
versions NotebookPageVersion[]
@@index([notebookId])
@@index([pageNumber])
}
model NotebookPageVersion {
id String @id @default(cuid())
pageId String
version Int
textContent String?
drawingData String?
editedBy String?
editSummary String?
createdAt DateTime @default(now())
page NotebookPage @relation(fields: [pageId], references: [id], onDelete: Cascade)
@@index([pageId, version])
@@map("notebook_page_versions")
}
// ─── Drawing Canvas ────────────────────────────────────────────────
model Drawing {
id String @id @default(cuid())
schoolId String
ownerId String
title String
description String?
drawingData String // JSON with all strokes, paths, colors
imageData String? // base64 encoded PNG thumbnail
subjectId String?
classGroupId String?
isPublic Boolean @default(false)
isDemo Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
school School @relation(fields: [schoolId], references: [id])
owner User @relation(fields: [ownerId], references: [id])
subject Subject? @relation(fields: [subjectId], references: [id])
classGroup ClassGroup? @relation(fields: [classGroupId], references: [id])
@@index([schoolId])
@@index([ownerId])
@@index([subjectId])
}
model Notification {
id String @id @default(cuid())
schoolId String
userId String // recipient
type String // ASSESSMENT_DUE, MISSING_OBSERVATION, NOTEBOOK_SHARED, BEHAVIOR_ALERT, GRADE_COMPUTED, etc.
title String
message String
isRead Boolean @default(false)
actionUrl String? // optional deep link
relatedId String? // optional related entity ID
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
school School @relation(fields: [schoolId], references: [id])
user User @relation(fields: [userId], references: [id])
@@index([userId, isRead])
@@index([schoolId])
@@map("notifications")
}
// ─── Calendar Events ──────────────────────────────────────────────
model CalendarEvent {
id String @id @default(cuid())
schoolId String
teacherId String
title String
date DateTime
startTime String? // HH:mm format, null = all day
endTime String? // HH:mm format, null = all day
eventType String @default("reminder") // assessment | lesson | reminder
subjectId String?
classGroupId String?
notes String?
allDay Boolean @default(false)
recurrencePattern String? // JSON: { type: "daily|weekly|monthly|yearly", interval: 1, endDate?: string, daysOfWeek?: number[] }
recurrenceEnd DateTime? // When recurrence ends
parentEventId String? // For generated instances of a recurring event
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
school School @relation(fields: [schoolId], references: [id])
teacher User @relation(fields: [teacherId], references: [id])
subject Subject? @relation(fields: [subjectId], references: [id])
classGroup ClassGroup? @relation(fields: [classGroupId], references: [id])
parentEvent CalendarEvent? @relation("EventRecurrence", fields: [parentEventId], references: [id])
childEvents CalendarEvent[] @relation("EventRecurrence")
@@index([schoolId])
@@index([teacherId])
@@index([date])
@@index([subjectId])
@@index([classGroupId])
@@index([parentEventId])
}
// ─── Parent-Student Link ──────────────────────────────────────────────
model ParentStudentLink {
id String @id @default(cuid())
parentId String // User with PARENT role
studentId String // Student record
schoolId String
relationship String? // mother, father, guardian, etc.
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
parent User @relation(fields: [parentId], references: [id])
student Student @relation(fields: [studentId], references: [id])
school School @relation(fields: [schoolId], references: [id])
@@unique([parentId, studentId])
@@map("parent_student_links")
}
// ─── Curriculum Standards ──────────────────────────────────────────
model CurriculumStandard {
id String @id @default(cuid())
schoolId String
subjectId String?
code String // e.g., "M.1.1", "D.2.3"
title String
description String?
gradeLevel Int?
category String? // e.g., "Number", "Algebra", "Geometry"
source String? // e.g., "KMK", "Lehrplan NRW"
isDemo Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
school School @relation(fields: [schoolId], references: [id])
subject Subject? @relation(fields: [subjectId], references: [id])
competencyLinks CurriculumStandardLink[]
@@index([schoolId, subjectId])
@@map("curriculum_standards")
}
model CurriculumStandardLink {
id String @id @default(cuid())
standardId String
competencyId String
coverageLevel String? // "full", "partial", "related"
notes String?
createdAt DateTime @default(now())
standard CurriculumStandard @relation(fields: [standardId], references: [id], onDelete: Cascade)
competency Competency @relation(fields: [competencyId], references: [id], onDelete: Cascade)
@@unique([standardId, competencyId])
@@map("curriculum_standard_links")
}
// ─── Data Backup ─────────────────────────────────────────────────────
model Backup {
id String @id @default(cuid())
schoolId String
filename String
size Int // bytes
type String // "full", "incremental"
status String // "pending", "completed", "failed"
notes String?
createdAt DateTime @default(now())
school School @relation(fields: [schoolId], references: [id], onDelete: Cascade)
@@map("backups")
}
// ─── Behavior Interventions ─────────────────────────────────────────
model BehaviorIntervention {
id String @id @default(cuid())
schoolId String
studentId String
incidentId String?
type String // "warning", "meeting", "action_plan", "support", "parent_contact"
description String
status String @default("planned") // "planned", "in_progress", "completed", "cancelled"
assignedTo String?
startDate DateTime?
endDate DateTime?
outcome String?
isDemo Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
school School @relation(fields: [schoolId], references: [id])
student Student @relation(fields: [studentId], references: [id])
incident BehaviorIncident? @relation(fields: [incidentId], references: [id])
assignedUser User? @relation("InterventionAssignee", fields: [assignedTo], references: [id])
@@index([schoolId, studentId])
@@map("behavior_interventions")
}
// ─── Report Scheduling ──────────────────────────────────────────────
model ReportSchedule {
id String @id @default(cuid())
schoolId String
classGroupId String?
template String // "short", "full", "custom"
frequency String // "weekly", "monthly", "quarterly", "end_of_term"
dayOfWeek Int? // 0-6 for weekly
dayOfMonth Int? // 1-31 for monthly
monthOfYear Int? // 1-12 for quarterly
recipients String? // JSON array of userIds
includeStudents Boolean @default(true)
includeGrades Boolean @default(true)
includeAttendance Boolean @default(true)
includeBehavior Boolean @default(false)
includeCompetencies Boolean @default(true)
isActive Boolean @default(true)
lastRunAt DateTime?
nextRunAt DateTime?
isDemo Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
school School @relation(fields: [schoolId], references: [id])
classGroup ClassGroup? @relation(fields: [classGroupId], references: [id])
@@index([schoolId, isActive])
@@map("report_schedules")
}
// ─── School District ────────────────────────────────────────────────
model SchoolDistrict {
id String @id @default(cuid())
name String
code String? // district code
region String?
country String @default("DE")
adminEmail String?
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
schools School[]
@@map("school_districts")
}
// ─── Homework Management ──────────────────────────────────────────
model Homework {
id String @id @default(cuid())
schoolId String
classGroupId String
subjectId String?
teacherId String
title String
description String?
dueDate DateTime
homeworkType String @default("assignment") // assignment, reading, project, practice, research
maxPoints Float?
attachments String? // JSON array of attachment URLs
isPublished Boolean @default(true)
isDemo Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
school School @relation(fields: [schoolId], references: [id])
classGroup ClassGroup @relation(fields: [classGroupId], references: [id])
subject Subject? @relation(fields: [subjectId], references: [id])
teacher User @relation(fields: [teacherId], references: [id])
submissions HomeworkSubmission[]
@@index([schoolId, classGroupId, dueDate])
@@map("homework")
}
model HomeworkSubmission {
id String @id @default(cuid())
homeworkId String
studentId String
content String? // text submission
attachments String? // JSON array of attachment URLs
status String @default("pending") // pending, submitted, graded, late
score Float?
feedback String?
submittedAt DateTime?
gradedAt DateTime?
isDemo Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
homework Homework @relation(fields: [homeworkId], references: [id], onDelete: Cascade)
student Student @relation(fields: [studentId], references: [id])
@@unique([homeworkId, studentId])
@@index([homeworkId, status])
@@map("homework_submissions")
}
// ─── Announcements ────────────────────────────────────────────────
model Announcement {
id String @id @default(cuid())
schoolId String
authorId String
title String
content String
priority String @default("normal") // low, normal, high, urgent
targetAudience String @default("all") // all, teachers, students, parents, class
classGroupId String? // if target is specific class
isPinned Boolean @default(false)
expiresAt DateTime?
isDemo Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
school School @relation(fields: [schoolId], references: [id])
author User @relation(fields: [authorId], references: [id])
classGroup ClassGroup? @relation(fields: [classGroupId], references: [id])
@@index([schoolId, createdAt])
@@map("announcements")
}
// ─── Student Self-Assessment ──────────────────────────────────────────
model SelfAssessment {
id String @id @default(cuid())
schoolId String
studentId String
competencyId String
classGroupId String?
selfLevel Int // 1-6 self-assessed mastery
confidence Int? // 1-5 confidence level
reflection String? // student's self-reflection text
evidence String? // what evidence supports this assessment
goalId String? // linked learning goal
isDemo Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
school School @relation(fields: [schoolId], references: [id])
student Student @relation(fields: [studentId], references: [id])
competency Competency @relation(fields: [competencyId], references: [id])
classGroup ClassGroup? @relation(fields: [classGroupId], references: [id])
goal LearningGoal? @relation(fields: [goalId], references: [id])
@@index([schoolId, studentId])
@@map("self_assessments")
}
// ─── Learning Goals ────────────────────────────────────────────────
model LearningGoal {
id String @id @default(cuid())
schoolId String
studentId String
competencyId String?
title String
description String?
targetLevel Int? // target mastery level
currentLevel Int? // current mastery level
deadline DateTime?
status String @default("active") // active, completed, abandoned
progress Int @default(0) // 0-100
isDemo Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
school School @relation(fields: [schoolId], references: [id])
student Student @relation(fields: [studentId], references: [id])
competency Competency? @relation(fields: [competencyId], references: [id])
selfAssessments SelfAssessment[]
@@index([schoolId, studentId, status])
@@map("learning_goals")
}
// ─── Portfolio Entries ──────────────────────────────────────────────
model PortfolioEntry {
id String @id @default(cuid())
schoolId String
studentId String
title String
description String?
entryType String // "artwork", "writing", "project", "presentation", "achievement", "reflection"
competencyId String?
content String? // text content or description
mediaUrls String? // JSON array of media URLs
notebookPageId String? // link to notebook page
drawingId String? // link to drawing
isPublic Boolean @default(false)
tags String? // JSON array of tags
isDemo Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
school School @relation(fields: [schoolId], references: [id])
student Student @relation(fields: [studentId], references: [id])
competency Competency? @relation(fields: [competencyId], references: [id])
@@index([schoolId, studentId])
@@map("portfolio_entries")
}
// ─── Timetable ──────────────────────────────────────────────────────
model TimetableSlot {
id String @id @default(cuid())
schoolId String
classGroupId String
dayOfWeek Int // 0=Monday, 1=Tuesday, ..., 4=Friday
period Int // 1-8 (lesson period)
subjectId String?
teacherId String?
roomId String? // optional room
startTime String // "08:00"
endTime String // "08:45"
isBreak Boolean @default(false)
isDemo Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
school School @relation(fields: [schoolId], references: [id])
classGroup ClassGroup @relation(fields: [classGroupId], references: [id])
subject Subject? @relation(fields: [subjectId], references: [id])
teacher User? @relation(fields: [teacherId], references: [id])
@@unique([classGroupId, dayOfWeek, period])
@@index([schoolId, classGroupId])
@@map("timetable_slots")
}
// ─── Digital Resource Library ──────────────────────────────────────
model Resource {
id String @id @default(cuid())
schoolId String
authorId String
title String
description String?
resourceType String // "document", "worksheet", "presentation", "video_link", "image", "link", "audio"
url String? // external URL or file path
content String? // text content
subjectId String?
classGroupId String?
gradeLevel Int?
tags String? // JSON array of tags
isPublic Boolean @default(true)
downloadCount Int @default(0)
isDemo Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
school School @relation(fields: [schoolId], references: [id])
author User @relation(fields: [authorId], references: [id])
subject Subject? @relation(fields: [subjectId], references: [id])
classGroup ClassGroup? @relation(fields: [classGroupId], references: [id])
@@index([schoolId, resourceType])
@@map("resources")
}
// ─── Email Templates & Logging ───────────────────────────────────────
model EmailTemplate {
id String @id @default(cuid())
schoolId String
name String // "weekly_report", "assessment_reminder", "behavior_alert", "attendance_notice", "welcome"
subject String
body String // HTML template with {{variables}}
isDefault Boolean @default(false)
isDemo Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
school School @relation(fields: [schoolId], references: [id])
@@unique([schoolId, name])
@@map("email_templates")
}
model EmailLog {
id String @id @default(cuid())
schoolId String
templateId String?
recipientEmail String
recipientName String?
subject String
body String
status String @default("pending") // pending, sent, failed, bounced
sentAt DateTime?
error String?
createdAt DateTime @default(now())
school School @relation(fields: [schoolId], references: [id])
@@index([schoolId, status])
@@map("email_logs")
}
// ─── Peer Assessment ──────────────────────────────────────────────
model PeerAssessment {
id String @id @default(cuid())
schoolId String
assessorId String // student who is assessing
assessedId String // student being assessed
competencyId String?
classGroupId String?
assessmentType String // "competency", "project", "presentation", "teamwork", "behavior"
level Int? // 1-6 rating
comment String?
rubricId String? // optional rubric reference
isAnonymous Boolean @default(false)
isDemo Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
school School @relation(fields: [schoolId], references: [id])
assessor Student @relation("PeerAssessor", fields: [assessorId], references: [id])
assessed Student @relation("PeerAssessed", fields: [assessedId], references: [id])
competency Competency? @relation(fields: [competencyId], references: [id])
classGroup ClassGroup? @relation(fields: [classGroupId], references: [id])
rubric Rubric? @relation(fields: [rubricId], references: [id])
@@index([schoolId, assessedId])
@@index([schoolId, assessorId])
@@map("peer_assessments")
}
// ─── Emergency Contacts ───────────────────────────────────────────
model EmergencyContact {
id String @id @default(cuid())
schoolId String
studentId String
name String
relationship String // "mother", "father", "guardian", "grandparent", "other"
phone String
phoneAlt String?
email String?
address String?
isPrimary Boolean @default(false)
priority Int @default(1) // 1=first call, 2=second call, etc.
notes String?
isDemo Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
school School @relation(fields: [schoolId], references: [id])
student Student @relation(fields: [studentId], references: [id])
@@index([schoolId, studentId])
@@map("emergency_contacts")
}
// ─── School Events ────────────────────────────────────────────────
model SchoolEvent {
id String @id @default(cuid())
schoolId String
title String
description String?
eventType String // "assembly", "field_trip", "sports_day", "concert", "fair", "parent_meeting", "graduation", "holiday"
startDate DateTime
endDate DateTime?
location String?
organizerId String?
classGroupId String? // if event is for specific class
isAllSchool Boolean @default(true)
isPublic Boolean @default(true)
requiresRegistration Boolean @default(false)
maxParticipants Int?
notes String?
isDemo Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
school School @relation(fields: [schoolId], references: [id])
organizer User? @relation(fields: [organizerId], references: [id])
classGroup ClassGroup? @relation(fields: [classGroupId], references: [id])
registrations EventRegistration[]
@@index([schoolId, startDate])
@@map("school_events")
}
model EventRegistration {
id String @id @default(cuid())
eventId String
userId String
status String @default("registered") // registered, cancelled, attended
notes String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
event SchoolEvent @relation(fields: [eventId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id])
@@unique([eventId, userId])
@@map("event_registrations")
}
// ─── Student Transportation ──────────────────────────────────────
model StudentTransport {
id String @id @default(cuid())
schoolId String
studentId String
transportType String // "bus", "tram", "walk", "car", "bike", "other"
routeNumber String? // bus/tram route number
stopName String? // nearest stop name
pickupTime String? // "07:45"
dropoffTime String? // "15:30"
driverName String? // for car/bus
driverPhone String? // for car/bus
distanceKm Float? // distance in km
notes String?
isDemo Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
school School @relation(fields: [schoolId], references: [id])
student Student @relation(fields: [studentId], references: [id])
@@index([schoolId, studentId])
@@map("student_transport")
}
// ─── Health Records ──────────────────────────────────────────────
model HealthRecord {
id String @id @default(cuid())
schoolId String
studentId String
bloodType String? // "A+", "A-", "B+", "B-", "O+", "O-", "AB+", "AB-"
allergies String? // JSON array of allergy names
medications String? // JSON array of { name, dosage, frequency }
conditions String? // JSON array of condition names
doctorName String?
doctorPhone String?
insuranceNumber String?
insuranceProvider String?
lastCheckup DateTime?
notes String?
isConfidential Boolean @default(true)
isDemo Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
school School @relation(fields: [schoolId], references: [id])
student Student @relation(fields: [studentId], references: [id])
@@unique([schoolId, studentId])
@@map("health_records")
}
// ─── Badges & Gamification ───────────────────────────────────────
model Badge {
id String @id @default(cuid())
schoolId String
name String
description String?
icon String // Lucide icon name
color String @default("#10b981")
category String // "competency", "attendance", "behavior", "achievement", "milestone"
requirementType String // "mastery_level", "attendance_rate", "behavior_count", "progress_entries", "custom"
requirementValue Int? // threshold value
isAuto Boolean @default(true) // auto-awarded or manual
isDemo Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
school School @relation(fields: [schoolId], references: [id])
studentBadges StudentBadge[]
@@index([schoolId, category])
@@map("badges")
}
model StudentBadge {
id String @id @default(cuid())
schoolId String
studentId String
badgeId String
awardedAt DateTime @default(now())
awardedBy String? // userId who awarded (null if auto)
notes String?
isDemo Boolean @default(false)
createdAt DateTime @default(now())
school School @relation(fields: [schoolId], references: [id])
student Student @relation(fields: [studentId], references: [id])
badge Badge @relation(fields: [badgeId], references: [id])
awardedByUser User? @relation(fields: [awardedBy], references: [id])
@@unique([studentId, badgeId])
@@index([schoolId, studentId])
@@map("student_badges")
}