Deleted orphan component files (4): - dashboard-customizer.tsx (never imported) - unified-communication.tsx (never imported) - data-table.tsx (never imported) - widget-container.tsx (only imported by orphan dashboard-customizer) Deleted unused shadcn/ui primitives (15): - accordion, aspect-ratio, calendar, carousel, chart, form, hover-card, input-otp, menubar, navigation-menu, pagination, radio-group, resizable, toaster, toggle-group (All verified as zero importers) Deleted orphan API route groups (6 directories, 13 route files): - ai-grading-audit (no caller) - demo-data (no caller) - exam-plans (no caller) - grade-analytics (no caller, dead renderView case also removed) - grading-annotations (no caller) - grading-reviews (no caller) Removed dead grade-analytics renderView case + ViewName type entry Removed 10 orphan database models from schema: - NotificationHub, NotificationArchive (superseded by Notification) - WidgetTemplate (dashboard customizer feature abandoned) - EmergencySignage, SignageMessage, SignageSchedule, SignageAuditLog (signage feature - no API routes implemented) - SchoolPolicy, EscalationPolicy, ClassMessagingLock (policy/messaging lock feature - never wired) Also removed 16 relation fields pointing to deleted models. Schema validated, db pushed, lint passes, all 10 tested modules work.
3561 lines
133 KiB
Plaintext
3561 lines
133 KiB
Plaintext
// SchulOS — 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[]
|
|
reportCardTemplates ReportCardTemplate[]
|
|
homeworks Homework[]
|
|
announcements Announcement[]
|
|
notificationPreferences NotificationPreferences[]
|
|
selfAssessments SelfAssessment[]
|
|
learningGoals LearningGoal[]
|
|
portfolioEntries PortfolioEntry[]
|
|
timetableSlots TimetableSlot[]
|
|
resources Resource[]
|
|
emailTemplates EmailTemplate[]
|
|
emailLogs EmailLog[]
|
|
peerAssessments PeerAssessment[]
|
|
peerAssessmentSessions PeerAssessmentSession[]
|
|
emergencyContacts EmergencyContact[]
|
|
schoolEvents SchoolEvent[]
|
|
studentTransports StudentTransport[]
|
|
transportRoutes TransportRoute[]
|
|
healthRecords HealthRecord[]
|
|
badges Badge[]
|
|
studentBadges StudentBadge[]
|
|
newsletters Newsletter[]
|
|
competitions Competition[]
|
|
rewardClaims RewardClaim[]
|
|
rewards Reward[]
|
|
rewardPoints RewardPoints[]
|
|
subjectTopics SubjectTopic[]
|
|
district SchoolDistrict? @relation(fields: [districtId], references: [id])
|
|
districtId String?
|
|
// New features
|
|
subjectCategories SubjectCategory[]
|
|
subjectContents SubjectContent[]
|
|
contentChangeRequests ContentChangeRequest[]
|
|
aiSettings AISettings?
|
|
virtualCharacters VirtualCharacter[]
|
|
illnessReports IllnessReport[]
|
|
counselingAppointments CounselingAppointment[]
|
|
disciplinaryCommittees DisciplinaryCommittee[]
|
|
communicationRooms CommunicationRoom[]
|
|
gradeReports GradeReport[]
|
|
aiTestGenerations AITestGeneration[]
|
|
gradingReviews TeacherGradingReview[]
|
|
gradingAnnotations GradingAnnotation[]
|
|
chatMessages ChatMessage[]
|
|
examPlans ExamPlan[]
|
|
studyPlans StudyPlan[]
|
|
studySessions StudySession[]
|
|
libraryBooks LibraryBook[]
|
|
bookCheckouts BookCheckout[]
|
|
bookReservations BookReservation[]
|
|
seatingCharts SeatingChart[]
|
|
dataImportJobs DataImportJob[]
|
|
dataExportJobs DataExportJob[]
|
|
wellnessCheckins WellnessCheckin[]
|
|
wellnessScores WellnessScore[]
|
|
careerProfiles CareerProfile[]
|
|
careerAppointments CareerAppointment[]
|
|
substituteTeachers SubstituteTeacher[]
|
|
teacherAbsences TeacherAbsence[]
|
|
substitutionAssignments SubstitutionAssignment[]
|
|
// Module L compliance relations
|
|
dataRetentionPolicies DataRetentionPolicy[]
|
|
dpiaRecords DpiaRecord[]
|
|
complianceConsultations ComplianceConsultation[]
|
|
complianceStatus SchoolComplianceStatus?
|
|
deletionFlags DeletionFlag[]
|
|
// Module B signage relations
|
|
// Module F exam mode relations
|
|
examSessions ExamSession[]
|
|
// Module H notifications relations
|
|
// Module J widgets relations
|
|
dashboardWidgets DashboardWidget[]
|
|
dashboardLayouts DashboardLayout[]
|
|
}
|
|
|
|
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, VICE_PRINCIPAL, DPO
|
|
locale String @default("de")
|
|
twoFactorSecret String?
|
|
emailVerifiedAt DateTime?
|
|
isDemo Boolean @default(false)
|
|
isTeacher Boolean @default(false) // civil-servant status for Personalrat rights
|
|
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[]
|
|
notificationPreferences NotificationPreferences?
|
|
timetableSlots TimetableSlot[]
|
|
resources Resource[]
|
|
organizedSchoolEvents SchoolEvent[]
|
|
eventRegistrations EventRegistration[]
|
|
eventFeedbacks EventFeedback[]
|
|
awardedBadges StudentBadge[]
|
|
newsletters Newsletter[]
|
|
competitionsCreated Competition[]
|
|
competitionParticipations CompetitionParticipant[]
|
|
competitionClaims RewardClaim[]
|
|
rewardRedemptions RewardRedemption[]
|
|
rewardPoints RewardPoints[]
|
|
studentAnswers StudentAnswer[]
|
|
student Student?
|
|
// New features
|
|
contentChangeRequests ContentChangeRequest[] @relation("ContentRequester")
|
|
contentChangeReviews ContentChangeRequest[] @relation("ContentReviewer")
|
|
chatMessages ChatMessage[]
|
|
illnessReports IllnessReport[]
|
|
illnessReportApprovals IllnessReport[] @relation("IllnessApprover")
|
|
illnessReportAdminApprovals IllnessReport[] @relation("IllnessAdminApprover")
|
|
teacherAbsenceApprovals TeacherAbsence[] @relation("TeacherAbsenceApprover")
|
|
communicationRoomMembers CommunicationRoomMember[]
|
|
communicationMessages CommunicationMessage[]
|
|
counselingAppointments CounselingAppointment[] @relation("Counselor")
|
|
counselingStudentAppts CounselingAppointment[] @relation("CounselingStudent")
|
|
disciplinaryCasesReported DisciplinaryCase[] @relation("CaseReporter")
|
|
disciplinaryCasesReviewed DisciplinaryCase[] @relation("CaseReviewer")
|
|
disciplinaryCommitteeMembers DisciplinaryCommitteeMember[]
|
|
virtualCharacter VirtualCharacter?
|
|
aiTestGenerations AITestGeneration[]
|
|
gradingReviews TeacherGradingReview[]
|
|
gradingReviewComments GradingReviewComment[]
|
|
gradingAnnotations GradingAnnotation[]
|
|
announcementReads AnnouncementRead[]
|
|
selectedCharacterId String?
|
|
// Communication room relations
|
|
roomStudent CommunicationRoom[] @relation("RoomStudent")
|
|
roomTeacher CommunicationRoom[] @relation("RoomTeacher")
|
|
// Class responsible teacher
|
|
responsibleClasses ClassGroup[] @relation("ResponsibleTeacher")
|
|
examPlans ExamPlan[]
|
|
seatingCharts SeatingChart[]
|
|
peerAssessmentSessions PeerAssessmentSession[] @relation("PeerAssessmentTeacher")
|
|
bookCheckouts BookCheckout[]
|
|
gradeReports GradeReport[] @relation("GradeReportGenerator")
|
|
bookReservations BookReservation[]
|
|
dataImportJobs DataImportJob[]
|
|
dataExportJobs DataExportJob[]
|
|
careerAppointments CareerAppointment[]
|
|
substituteTeacher SubstituteTeacher?
|
|
teacherAbsences TeacherAbsence[]
|
|
// Module L compliance relations
|
|
retentionPoliciesReviewed DataRetentionPolicy[] @relation("DataRetentionPolicyReviewer")
|
|
dpiaRecordsApproved DpiaRecord[] @relation("DpiaRecordApprover")
|
|
complianceConsultationsAcknowledged ComplianceConsultation[] @relation("ComplianceConsultationAcknowledger")
|
|
deletionFlagsApproved DeletionFlag[] @relation("DeletionFlagApprover")
|
|
// Module B signage relations
|
|
// Module H notifications relations
|
|
// Module J widgets relations
|
|
dashboardWidgets DashboardWidget[]
|
|
dashboardLayouts DashboardLayout[]
|
|
passwordResetTokens PasswordResetToken[]
|
|
twoFactorRecoveryCodes TwoFactorRecoveryCode[]
|
|
}
|
|
|
|
model PasswordResetToken {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
tokenHash String @unique
|
|
expiresAt DateTime
|
|
usedAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([userId])
|
|
@@index([expiresAt])
|
|
}
|
|
|
|
model TwoFactorRecoveryCode {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
codeHash String @unique
|
|
usedAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([userId])
|
|
}
|
|
|
|
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
|
|
responsibleTeacherId String? // Class responsible teacher (Klassenlehrer)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
schoolYear SchoolYear @relation(fields: [schoolYearId], references: [id])
|
|
responsibleTeacher User? @relation("ResponsibleTeacher", fields: [responsibleTeacherId], 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[]
|
|
peerAssessmentSessions PeerAssessmentSession[] @relation("PeerAssessmentClassGroup")
|
|
schoolEvents SchoolEvent[]
|
|
aiTestGenerations AITestGeneration[]
|
|
communicationRooms CommunicationRoom[]
|
|
examPlans ExamPlan[]
|
|
seatingCharts SeatingChart[]
|
|
substitutionAssignments SubstitutionAssignment[]
|
|
}
|
|
|
|
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
|
|
avatarUrl String? // student photo/avatar URL
|
|
avatarInitials String? // computed from firstName + lastName first chars
|
|
userId String? @unique // optional link to User account (for student self-service)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
deletedAt DateTime?
|
|
|
|
user User? @relation(fields: [userId], references: [id])
|
|
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[]
|
|
goals StudentGoal[]
|
|
illnessReports IllnessReport[]
|
|
disciplinaryCases DisciplinaryCase[]
|
|
studyPlans StudyPlan[]
|
|
studySessions StudySession[]
|
|
gradingAnnotations GradingAnnotation[]
|
|
bookCheckouts BookCheckout[]
|
|
bookReservations BookReservation[]
|
|
wellnessCheckins WellnessCheckin[]
|
|
wellnessScores WellnessScore[]
|
|
careerProfile CareerProfile?
|
|
// Module F exam mode relations
|
|
examSessions ExamSession[]
|
|
|
|
@@index([userId])
|
|
}
|
|
|
|
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[]
|
|
topics SubjectTopic[]
|
|
competitions Competition[]
|
|
subjectContents SubjectContent[]
|
|
aiTestGenerations AITestGeneration[]
|
|
examPlans ExamPlan[]
|
|
studyPlans StudyPlan[]
|
|
studySessions StudySession[]
|
|
substitutionAssignments SubstitutionAssignment[]
|
|
}
|
|
|
|
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[]
|
|
aiTestGenerations AITestGeneration[]
|
|
gradingReviews TeacherGradingReview[]
|
|
gradingAnnotations GradingAnnotation[]
|
|
examPlan ExamPlan?
|
|
// Module F exam mode relations
|
|
examSessions ExamSession[]
|
|
}
|
|
|
|
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?
|
|
annotationData String? // JSON: drawing annotation strokes for teacher grading
|
|
annotationImage String? // base64 PNG thumbnail of annotation
|
|
|
|
assessment Assessment @relation(fields: [assessmentId], references: [id])
|
|
student Student @relation(fields: [studentId], references: [id])
|
|
annotations GradingAnnotation[]
|
|
|
|
@@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, REVIEW, PUBLISHED, ARCHIVED, FINAL
|
|
pdfFilePath String?
|
|
includesGrades Boolean @default(false)
|
|
|
|
// Report card extended fields
|
|
teacherComments String? // teacher comments text
|
|
attendanceSummary String? // JSON: { present: N, absent: N, excused: N, late: N, total: N }
|
|
overallAssessment String? // overall assessment text
|
|
templateId String? // report card template reference
|
|
reviewedByUserId String? // reviewer user ID
|
|
reviewedAt DateTime? // review timestamp
|
|
publishedAt DateTime? // publish timestamp
|
|
|
|
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[]
|
|
template ReportCardTemplate? @relation(fields: [templateId], references: [id])
|
|
}
|
|
|
|
model ReportCardTemplate {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
name String
|
|
description String?
|
|
sections String @default("[]") // JSON: array of section definitions
|
|
gradingScale String @default("{}")
|
|
layout String @default("default") // default, detailed, compact
|
|
isDefault Boolean @default(false)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
reports Report[]
|
|
}
|
|
|
|
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
|
|
dataSubject String? // track which student (if applicable) for GDPR export scope
|
|
|
|
user User? @relation(fields: [userId], references: [id])
|
|
school School? @relation(fields: [schoolId], references: [id])
|
|
|
|
@@index([dataSubject])
|
|
@@index([timestamp])
|
|
}
|
|
|
|
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 | quiz | exercise | flashcard
|
|
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.
|
|
category String @default("academic") // academic, behavioral, administrative, calendar, communication, system
|
|
priority String @default("normal") // urgent, high, normal, low
|
|
title String
|
|
message String
|
|
isRead Boolean @default(false)
|
|
isArchived 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])
|
|
@@index([category])
|
|
@@index([priority])
|
|
@@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 | exam
|
|
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
|
|
assessmentId String? // Linked assessment (for exam events)
|
|
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")
|
|
examPlan ExamPlan?
|
|
illnessReport IllnessReport?
|
|
teacherAbsence TeacherAbsence? @relation("TeacherAbsenceCalendar")
|
|
|
|
@@index([schoolId])
|
|
@@index([teacherId])
|
|
@@index([date])
|
|
@@index([subjectId])
|
|
@@index([classGroupId])
|
|
@@index([parentEventId])
|
|
@@index([eventType])
|
|
}
|
|
|
|
// ─── 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)
|
|
// AI-generated content linked from SubjectLesson
|
|
aiLessonId String? // link to SubjectLesson with AI content
|
|
aiVideoUrl String? // direct AI video URL for this homework
|
|
aiImageUrl String? // direct AI image URL for this homework
|
|
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
|
|
announcementType String @default("general") // general, urgent, event, holiday, exam, deadline
|
|
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])
|
|
reads AnnouncementRead[]
|
|
|
|
@@index([schoolId, createdAt])
|
|
@@map("announcements")
|
|
}
|
|
|
|
model AnnouncementRead {
|
|
id String @id @default(cuid())
|
|
announcementId String
|
|
userId String
|
|
readAt DateTime @default(now())
|
|
|
|
announcement Announcement @relation(fields: [announcementId], references: [id], onDelete: Cascade)
|
|
user User @relation(fields: [userId], references: [id])
|
|
|
|
@@unique([announcementId, userId])
|
|
@@index([announcementId])
|
|
@@index([userId])
|
|
@@map("announcement_reads")
|
|
}
|
|
|
|
model NotificationPreferences {
|
|
id String @id @default(cuid())
|
|
userId String @unique
|
|
schoolId String
|
|
academicEnabled Boolean @default(true)
|
|
behavioralEnabled Boolean @default(true)
|
|
administrativeEnabled Boolean @default(true)
|
|
calendarEnabled Boolean @default(true)
|
|
communicationEnabled Boolean @default(true)
|
|
systemEnabled Boolean @default(true)
|
|
quietHoursStart String? // HH:mm format, e.g. "22:00"
|
|
quietHoursEnd String? // HH:mm format, e.g. "07:00"
|
|
quietHoursEnabled Boolean @default(false)
|
|
emailDigestEnabled Boolean @default(false)
|
|
emailDigestFrequency String @default("daily") // daily, weekly, never
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
|
|
@@index([userId])
|
|
@@map("notification_preferences")
|
|
}
|
|
|
|
// ─── 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
|
|
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 PeerAssessmentSession {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
teacherId String? // teacher who created the session
|
|
classGroupId String?
|
|
title String
|
|
description String?
|
|
assessmentType String @default("competency") // competency, project, presentation, teamwork
|
|
criteria String? // JSON: [{ name, description, maxScore }]
|
|
anonymityMode String @default("anonymous") // anonymous, semi-anonymous, open
|
|
status String @default("active") // active, completed, closed
|
|
deadline DateTime?
|
|
assignMode String @default("manual") // random, manual, group-based
|
|
assignedPairs String? // JSON: [{ assessorId, assessedId }]
|
|
isDemo Boolean @default(false)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
deletedAt DateTime?
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
teacher User? @relation("PeerAssessmentTeacher", fields: [teacherId], references: [id])
|
|
classGroup ClassGroup? @relation("PeerAssessmentClassGroup", fields: [classGroupId], references: [id])
|
|
peerAssessments PeerAssessment[]
|
|
|
|
@@index([schoolId, status])
|
|
@@index([schoolId, teacherId])
|
|
@@map("peer_assessment_sessions")
|
|
}
|
|
|
|
model PeerAssessment {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
sessionId String? // link to session
|
|
assessorId String // student who is assessing
|
|
assessedId String // student being assessed
|
|
competencyId String?
|
|
classGroupId String?
|
|
assessmentType String // "competency", "project", "presentation", "teamwork", "behavior"
|
|
criteria String? // JSON: [{ name, score, maxScore }] — individual criterion scores
|
|
level Int? // 1-6 rating (overall)
|
|
comment String?
|
|
rubricId String? // optional rubric reference
|
|
isAnonymous Boolean @default(false)
|
|
status String @default("pending") // pending, submitted
|
|
isDemo Boolean @default(false)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
deletedAt DateTime?
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
session PeerAssessmentSession? @relation(fields: [sessionId], 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])
|
|
@@index([sessionId])
|
|
@@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", "cultural", "academic", "social"
|
|
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?
|
|
budget Float?
|
|
registrationDeadline DateTime?
|
|
capacity Int?
|
|
isRecurring Boolean @default(false)
|
|
recurrenceRule String? // JSON: { type: "weekly", interval: 1, days: ["mon","wed"] }
|
|
bannerImageUrl String?
|
|
status String @default("draft") // draft, published, cancelled, completed
|
|
feedbackForm String? // JSON: feedback questions
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
organizer User? @relation(fields: [organizerId], references: [id])
|
|
classGroup ClassGroup? @relation(fields: [classGroupId], references: [id])
|
|
registrations EventRegistration[]
|
|
feedbacks EventFeedback[]
|
|
|
|
@@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")
|
|
}
|
|
|
|
model EventFeedback {
|
|
id String @id @default(cuid())
|
|
eventId String
|
|
userId String
|
|
rating Int // 1-5
|
|
comment String?
|
|
createdAt DateTime @default(now())
|
|
|
|
event SchoolEvent @relation(fields: [eventId], references: [id], onDelete: Cascade)
|
|
user User @relation(fields: [userId], references: [id])
|
|
|
|
@@unique([eventId, userId])
|
|
@@map("event_feedbacks")
|
|
}
|
|
|
|
// ─── 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
|
|
routeId String? // link to TransportRoute
|
|
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])
|
|
route TransportRoute? @relation(fields: [routeId], references: [id])
|
|
|
|
@@index([schoolId, studentId])
|
|
@@map("student_transport")
|
|
}
|
|
|
|
model TransportRoute {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
routeNumber String
|
|
routeName String
|
|
transportType String // bus, tram, train
|
|
driverName String?
|
|
driverPhone String?
|
|
capacity Int @default(40)
|
|
isActive Boolean @default(true)
|
|
notes String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
deletedAt DateTime?
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
stops TransportStop[]
|
|
assignments StudentTransport[]
|
|
|
|
@@index([schoolId])
|
|
@@map("transport_route")
|
|
}
|
|
|
|
model TransportStop {
|
|
id String @id @default(cuid())
|
|
routeId String
|
|
stopName String
|
|
stopOrder Int
|
|
pickupTime String? // "07:45"
|
|
dropoffTime String? // "15:30"
|
|
address String?
|
|
latitude Float?
|
|
longitude Float?
|
|
createdAt DateTime @default(now())
|
|
|
|
route TransportRoute @relation(fields: [routeId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([routeId])
|
|
@@map("transport_stop")
|
|
}
|
|
|
|
// ─── 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")
|
|
}
|
|
|
|
// ─── Competition System ──────────────────────────────────────────────
|
|
|
|
model Competition {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
title String
|
|
description String?
|
|
competitionType String // "class" | "inter_class" | "inter_school"
|
|
category String // "academic" | "sports" | "creativity" | "citizenship" | "digital" | "reading" | "stem" | "other"
|
|
subjectId String? // optional subject link
|
|
status String @default("draft") // "draft" | "registration" | "active" | "completed" | "cancelled"
|
|
startDate DateTime
|
|
endDate DateTime
|
|
registrationDeadline DateTime?
|
|
maxParticipants Int?
|
|
scoringType String @default("points") // "points" | "rank" | "time" | "badge_count"
|
|
rules String? // markdown rules/description
|
|
isPublic Boolean @default(false) // visible to all schools?
|
|
isDemo Boolean @default(false)
|
|
isFederation Boolean @default(false) // inter-school district federation competition
|
|
federationSchedule String? // "weekly" | "monthly" | "quarterly"
|
|
createdById String // teacher/admin who created
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
deletedAt DateTime?
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
createdBy User @relation(fields: [createdById], references: [id])
|
|
subject Subject? @relation(fields: [subjectId], references: [id])
|
|
participants CompetitionParticipant[]
|
|
rewards CompetitionReward[]
|
|
leaderboard CompetitionLeaderboard[]
|
|
claims RewardClaim[]
|
|
|
|
@@index([schoolId])
|
|
@@index([status])
|
|
@@index([competitionType])
|
|
@@index([startDate])
|
|
@@map("competitions")
|
|
}
|
|
|
|
model CompetitionParticipant {
|
|
id String @id @default(cuid())
|
|
competitionId String
|
|
participantType String // "student" | "class_group" | "school"
|
|
participantId String // studentId, classGroupId, or schoolId depending on type
|
|
userId String? // the student's userId (for individual participation)
|
|
registeredAt DateTime @default(now())
|
|
score Int @default(0)
|
|
rank Int?
|
|
isDisqualified Boolean @default(false)
|
|
notes String?
|
|
isDemo Boolean @default(false)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
competition Competition @relation(fields: [competitionId], references: [id], onDelete: Cascade)
|
|
user User? @relation(fields: [userId], references: [id])
|
|
|
|
@@unique([competitionId, participantType, participantId])
|
|
@@index([competitionId])
|
|
@@index([userId])
|
|
@@map("competition_participants")
|
|
}
|
|
|
|
model CompetitionReward {
|
|
id String @id @default(cuid())
|
|
competitionId String
|
|
name String
|
|
description String?
|
|
rewardType String // "digital_code" | "badge" | "certificate" | "experience" | "physical"
|
|
rewardValue String? // promo code, badge ID, certificate URL, etc.
|
|
rewardProvider String? // "netflix" | "amazon" | "cinema" | "theater" | "concert" | "custom"
|
|
rankRequirement Int? // minimum rank to receive (1=first place, etc.)
|
|
pointsRequired Int? // minimum points to receive
|
|
quantity Int @default(1) // how many available
|
|
claimedCount Int @default(0)
|
|
imageUrl String? // reward image
|
|
isDemo Boolean @default(false)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
competition Competition @relation(fields: [competitionId], references: [id], onDelete: Cascade)
|
|
claims RewardClaim[]
|
|
|
|
@@index([competitionId])
|
|
@@map("competition_rewards")
|
|
}
|
|
|
|
model CompetitionLeaderboard {
|
|
id String @id @default(cuid())
|
|
competitionId String
|
|
participantType String // "student" | "class_group" | "school"
|
|
participantId String
|
|
participantName String // denormalized for fast reads
|
|
score Int @default(0)
|
|
rank Int?
|
|
updatedAt DateTime @updatedAt
|
|
|
|
competition Competition @relation(fields: [competitionId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([competitionId, participantType, participantId])
|
|
@@index([competitionId, rank])
|
|
@@map("competition_leaderboard")
|
|
}
|
|
|
|
model RewardClaim {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
competitionId String
|
|
rewardId String
|
|
userId String // student who claimed
|
|
claimedAt DateTime @default(now())
|
|
code String? // the actual promo code (encrypted for DSGVO)
|
|
status String @default("pending") // "pending" | "claimed" | "expired" | "revoked"
|
|
expiresAt DateTime?
|
|
notes String?
|
|
isDemo Boolean @default(false)
|
|
createdAt DateTime @default(now())
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
user User @relation(fields: [userId], references: [id])
|
|
competition Competition @relation(fields: [competitionId], references: [id])
|
|
reward CompetitionReward @relation(fields: [rewardId], references: [id])
|
|
|
|
@@index([schoolId, userId])
|
|
@@index([competitionId])
|
|
@@map("reward_claims")
|
|
}
|
|
|
|
// ─── Newsletter / School Blog ──────────────────────────────────────
|
|
|
|
model Newsletter {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
authorId String
|
|
title String
|
|
subject String? // email subject line
|
|
content String // rich text content (HTML)
|
|
summary String? // short summary for preview
|
|
imageUrl String? // cover image URL
|
|
bannerImageUrl String? // banner image URL for newsletter header
|
|
category String @default("general") // general, academic, sports, arts, events, community
|
|
templateType String @default("monthly") // monthly, event, parent, emergency
|
|
targetAudience String? // JSON: { type: "all" | "classes" | "roles", ids: [...] }
|
|
status String @default("draft") // draft, scheduled, sent, archived
|
|
isPublished Boolean @default(false)
|
|
publishedAt DateTime?
|
|
scheduledAt DateTime?
|
|
sentAt DateTime?
|
|
tags String? // JSON array of tags
|
|
openCount Int @default(0)
|
|
clickCount Int @default(0)
|
|
bounceCount Int @default(0)
|
|
totalRecipients 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])
|
|
|
|
@@index([schoolId, isPublished, publishedAt])
|
|
@@index([schoolId, status])
|
|
@@map("newsletters")
|
|
}
|
|
|
|
// ─── Learning Content (German Curriculum) ─────────────────────────────
|
|
|
|
model SubjectTopic {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
subjectId String
|
|
title String
|
|
description String?
|
|
gradeLevel String? // "1" | "2" | "3" | "4" | "5" | "6" (German Grundschule)
|
|
curriculumCode String? // e.g., "DE-NRW-Math-1-01" (German state curriculum code)
|
|
sortOrder Int @default(0)
|
|
icon String? // Lucide icon name
|
|
color String @default("#10b981")
|
|
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])
|
|
lessons SubjectLesson[]
|
|
|
|
@@index([schoolId, subjectId])
|
|
@@index([gradeLevel])
|
|
@@map("subject_topics")
|
|
}
|
|
|
|
model SubjectLesson {
|
|
id String @id @default(cuid())
|
|
topicId String
|
|
title String
|
|
description String?
|
|
lessonType String @default("explanation") // "explanation" | "exercise" | "quiz" | "flashcard" | "video_link"
|
|
content String // markdown content for the lesson
|
|
difficulty String @default("medium") // "easy" | "medium" | "hard"
|
|
sortOrder Int @default(0)
|
|
estimatedMinutes Int? // estimated time to complete
|
|
isDemo Boolean @default(false)
|
|
// AI-generated content (created by teachers/admins)
|
|
aiVideoUrl String? // AI-generated explanatory video URL
|
|
aiImageUrl String? // AI-generated illustration image URL
|
|
aiThumbnailUrl String? // Video thumbnail
|
|
aiPrompt String? // Original prompt used for generation
|
|
aiProvider String? // "snapgen" | "perchance" | "zai"
|
|
aiGeneratedAt DateTime?
|
|
aiGeneratedBy String? // userId of teacher/admin who generated
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
deletedAt DateTime?
|
|
|
|
topic SubjectTopic @relation(fields: [topicId], references: [id], onDelete: Cascade)
|
|
questions LessonQuestion[]
|
|
|
|
@@index([topicId])
|
|
@@map("subject_lessons")
|
|
}
|
|
|
|
model LessonQuestion {
|
|
id String @id @default(cuid())
|
|
lessonId String
|
|
questionType String // "multiple_choice" | "true_false" | "fill_blank" | "short_answer" | "matching"
|
|
question String // the question text
|
|
options String? // JSON array of options for multiple choice
|
|
correctAnswer String // the correct answer(s)
|
|
explanation String? // why this answer is correct
|
|
points Int @default(1)
|
|
sortOrder Int @default(0)
|
|
isDemo Boolean @default(false)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
lesson SubjectLesson @relation(fields: [lessonId], references: [id], onDelete: Cascade)
|
|
answers StudentAnswer[]
|
|
|
|
@@index([lessonId])
|
|
@@map("lesson_questions")
|
|
}
|
|
|
|
model StudentAnswer {
|
|
id String @id @default(cuid())
|
|
questionId String
|
|
studentId String
|
|
answer String // the student's answer
|
|
isCorrect Boolean?
|
|
timeTakenMs Int? // how long the student took
|
|
attempts Int @default(1)
|
|
isDemo Boolean @default(false)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
question LessonQuestion @relation(fields: [questionId], references: [id], onDelete: Cascade)
|
|
student User @relation(fields: [studentId], references: [id])
|
|
|
|
@@unique([questionId, studentId])
|
|
@@index([studentId])
|
|
@@map("student_answers")
|
|
}
|
|
|
|
// ─── Digital Reward Catalog & Points System ──────────────────────────
|
|
|
|
model Reward {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
title String
|
|
description String?
|
|
category String // streaming, shopping, experience, merchandise, privilege
|
|
pointsCost Int
|
|
image String?
|
|
stock Int?
|
|
isActive Boolean @default(true)
|
|
isDemo Boolean @default(false)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
deletedAt DateTime?
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
redemptions RewardRedemption[]
|
|
|
|
@@index([schoolId])
|
|
@@index([category])
|
|
@@map("rewards")
|
|
}
|
|
|
|
model RewardRedemption {
|
|
id String @id @default(cuid())
|
|
rewardId String
|
|
userId String
|
|
pointsSpent Int
|
|
status String @default("pending") // pending, approved, rejected, fulfilled
|
|
note String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
reward Reward @relation(fields: [rewardId], references: [id])
|
|
user User @relation(fields: [userId], references: [id])
|
|
|
|
@@index([rewardId])
|
|
@@index([userId])
|
|
@@index([status])
|
|
@@map("reward_redemptions")
|
|
}
|
|
|
|
model RewardPoints {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
schoolId String
|
|
points Int @default(0)
|
|
source String // competition, grade, attendance, homework
|
|
sourceId String? // ID of the source entity
|
|
description String?
|
|
createdAt DateTime @default(now())
|
|
|
|
user User @relation(fields: [userId], references: [id])
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
|
|
@@index([userId])
|
|
@@index([schoolId])
|
|
@@index([source])
|
|
@@map("reward_points")
|
|
}
|
|
|
|
// ─── Student Goals (Student Portal) ─────────────────────────────────
|
|
|
|
model StudentGoal {
|
|
id String @id @default(cuid())
|
|
studentId String
|
|
title String
|
|
description String?
|
|
targetDate DateTime?
|
|
progress Int @default(0)
|
|
status String @default("active") // active, completed, abandoned
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
student Student @relation(fields: [studentId], references: [id])
|
|
|
|
@@index([studentId])
|
|
@@index([status])
|
|
}
|
|
|
|
// ─── Subject Content Management (Schlaukopf Clone) ──────────────────
|
|
|
|
model SubjectCategory {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
name String // e.g. "Klasse 5", "Klasse 6", "Oberstufe"
|
|
slug String // e.g. "klasse5", "klasse6", "oberstufe"
|
|
description String?
|
|
icon String? // Lucide icon name
|
|
sortOrder Int @default(0)
|
|
isActive Boolean @default(true)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
contents SubjectContent[]
|
|
|
|
@@index([schoolId])
|
|
@@index([slug])
|
|
@@map("subject_categories")
|
|
}
|
|
|
|
model SubjectContent {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
categoryId String
|
|
subjectId String? // Link to existing Subject
|
|
parentId String? // For nested topics
|
|
title String
|
|
slug String
|
|
description String?
|
|
icon String? // Lucide icon name
|
|
contentType String @default("topic") // topic, exercise, lesson, quiz, vocabulary
|
|
content String? // JSON content (questions, explanations, etc.)
|
|
difficulty String @default("medium") // easy, medium, hard
|
|
questionCount Int @default(0)
|
|
sortOrder Int @default(0)
|
|
isActive Boolean @default(true)
|
|
isPublic Boolean @default(true) // visible to all schools
|
|
sourceUrl String? // original schlaukopf.de URL
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
category SubjectCategory @relation(fields: [categoryId], references: [id])
|
|
parent SubjectContent? @relation("ContentHierarchy", fields: [parentId], references: [id])
|
|
children SubjectContent[] @relation("ContentHierarchy")
|
|
changeRequests ContentChangeRequest[]
|
|
subject Subject? @relation(fields: [subjectId], references: [id])
|
|
|
|
@@index([schoolId])
|
|
@@index([categoryId])
|
|
@@index([parentId])
|
|
@@index([slug])
|
|
@@index([contentType])
|
|
@@map("subject_contents")
|
|
}
|
|
|
|
model ContentChangeRequest {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
contentId String
|
|
requestedBy String
|
|
requestType String // edit, add, delete
|
|
title String
|
|
description String
|
|
proposedChanges String? // JSON with proposed changes
|
|
status String @default("pending") // pending, approved, rejected
|
|
reviewedBy String?
|
|
reviewNote String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
content SubjectContent @relation(fields: [contentId], references: [id])
|
|
requester User @relation("ContentRequester", fields: [requestedBy], references: [id])
|
|
reviewer User? @relation("ContentReviewer", fields: [reviewedBy], references: [id])
|
|
|
|
@@index([schoolId])
|
|
@@index([contentId])
|
|
@@index([status])
|
|
@@index([requestedBy])
|
|
@@map("content_change_requests")
|
|
}
|
|
|
|
// ─── AI Settings (Pollination, OpenAI, Anthropic) ────────────────────
|
|
|
|
model AISettings {
|
|
id String @id @default(cuid())
|
|
schoolId String @unique
|
|
// Pollination AI
|
|
pollinationEnabled Boolean @default(true)
|
|
pollinationApiKey String?
|
|
pollinationModel String @default("flux")
|
|
// OpenAI
|
|
openaiEnabled Boolean @default(false)
|
|
openaiApiKey String?
|
|
openaiModel String @default("gpt-4o")
|
|
// Anthropic
|
|
anthropicEnabled Boolean @default(false)
|
|
anthropicApiKey String?
|
|
anthropicModel String @default("claude-3-5-sonnet-20241022")
|
|
// AI Features
|
|
aiChatEnabled Boolean @default(true) // Student AI helper
|
|
aiImageGenEnabled Boolean @default(true) // Admin/Teacher image generation
|
|
aiVideoGenEnabled Boolean @default(false) // Admin/Teacher video generation
|
|
aiAutoTestEnabled Boolean @default(true) // Auto test generation before exams
|
|
aiGradingAuditEnabled Boolean @default(false) // AI grading audit
|
|
aiTopicGenEnabled Boolean @default(true) // Auto topic generation from categories
|
|
// Virtual Character
|
|
virtualCharacterEnabled Boolean @default(true) // Enable/disable for students
|
|
// Restrictions
|
|
aiMaxRequestsPerDay Int @default(50) // Student daily limit
|
|
aiHelperMode String @default("guided") // guided, full, restricted
|
|
aiSystemPrompt String? // Custom system prompt for AI chat
|
|
// SnapGen AI (Video generation)
|
|
snapgenEnabled Boolean @default(false)
|
|
snapgenApiKey String?
|
|
// Perchance AI (Image generation)
|
|
perchanceEnabled Boolean @default(false)
|
|
perchanceApiKey String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
|
|
@@map("ai_settings")
|
|
}
|
|
|
|
// ─── Virtual Character System ────────────────────────────────────────
|
|
|
|
model VirtualCharacter {
|
|
id String @id @default(cuid())
|
|
userId String @unique
|
|
schoolId String
|
|
characterId String // e.g. "owl", "dragon", "robot", "cat", "wizard"
|
|
name String // Custom name for the character
|
|
color String @default("#10b981") // Character accent color
|
|
level Int @default(1)
|
|
xp Int @default(0)
|
|
mood String @default("happy") // happy, excited, thinking, sleepy, celebrating
|
|
accessories String? // JSON array of unlocked accessories
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
user User @relation(fields: [userId], references: [id])
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
|
|
@@index([userId])
|
|
@@index([schoolId])
|
|
@@map("virtual_characters")
|
|
}
|
|
|
|
// ─── Chat Messages (AI Chatbot & Student-Teacher) ────────────────────
|
|
|
|
model ChatMessage {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
userId String
|
|
roomId String? // For room-based chat (null = AI chatbot)
|
|
content String
|
|
senderType String @default("user") // user, ai, teacher, system
|
|
metadata String? // JSON metadata (attachments, etc.)
|
|
isRead Boolean @default(false)
|
|
createdAt DateTime @default(now())
|
|
|
|
user User @relation(fields: [userId], references: [id])
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
room CommunicationRoom? @relation(fields: [roomId], references: [id])
|
|
|
|
@@index([userId])
|
|
@@index([roomId])
|
|
@@index([schoolId])
|
|
@@index([createdAt])
|
|
@@map("chat_messages")
|
|
}
|
|
|
|
// ─── Illness Reporting with Parent Approval ──────────────────────────
|
|
|
|
model IllnessReport {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
studentId String
|
|
reportedBy String // userId of the person reporting
|
|
reporterType String // student, parent
|
|
reason String // illness, doctor_visit, other
|
|
description String?
|
|
startDate DateTime
|
|
endDate DateTime?
|
|
documentUrl String? // Medical certificate URL
|
|
leaveType String @default("illness") // illness, personal_day, religious_holiday
|
|
parentApprovalStatus String @default("pending") // pending, approved, rejected
|
|
parentApprovedBy String? // userId of approving parent
|
|
parentApprovedAt DateTime?
|
|
// Status: only visible to student/parent until approved
|
|
isVisibleToTeacher Boolean @default(false)
|
|
isVisibleToAdmin Boolean @default(false)
|
|
teacherNotifiedAt DateTime?
|
|
adminApprovalStatus String @default("pending") // pending, approved, rejected
|
|
adminApprovedBy String?
|
|
adminApprovedAt DateTime?
|
|
adminNotes String?
|
|
calendarEventId String? @unique
|
|
status String @default("pending") // pending, approved, rejected, expired
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
student Student @relation(fields: [studentId], references: [id])
|
|
reporter User @relation(fields: [reportedBy], references: [id])
|
|
approver User? @relation("IllnessApprover", fields: [parentApprovedBy], references: [id])
|
|
adminApprover User? @relation("IllnessAdminApprover", fields: [adminApprovedBy], references: [id])
|
|
calendarEvent CalendarEvent? @relation(fields: [calendarEventId], references: [id])
|
|
|
|
@@index([schoolId])
|
|
@@index([studentId])
|
|
@@index([reportedBy])
|
|
@@index([parentApprovalStatus])
|
|
@@index([status])
|
|
@@index([adminApprovalStatus])
|
|
@@map("illness_reports")
|
|
}
|
|
|
|
// ─── Communication Rooms (Student-Teacher) ───────────────────────────
|
|
|
|
model CommunicationRoom {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
studentId String
|
|
teacherId String
|
|
roomType String @default("chat") // chat, voice, video
|
|
audienceType String @default("direct") // direct, class_group, parent_group, staff
|
|
classGroupId String?
|
|
status String @default("requested") // requested, active, closed
|
|
requestedBy String // student or teacher
|
|
escalationEligibleAt DateTime?
|
|
escalatedAt DateTime?
|
|
resolvedAt DateTime?
|
|
resolutionStatus String @default("open") // open, resolved, unresolved
|
|
acceptedAt DateTime?
|
|
closedAt DateTime?
|
|
closeReason String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
student User @relation("RoomStudent", fields: [studentId], references: [id])
|
|
teacher User @relation("RoomTeacher", fields: [teacherId], references: [id])
|
|
classGroup ClassGroup? @relation(fields: [classGroupId], references: [id])
|
|
members CommunicationRoomMember[]
|
|
messages CommunicationMessage[]
|
|
chatMessages ChatMessage[]
|
|
|
|
@@index([schoolId])
|
|
@@index([studentId])
|
|
@@index([teacherId])
|
|
@@index([status])
|
|
@@index([classGroupId])
|
|
@@index([escalationEligibleAt])
|
|
@@map("communication_rooms")
|
|
}
|
|
|
|
model CommunicationRoomMember {
|
|
id String @id @default(cuid())
|
|
roomId String
|
|
userId String
|
|
role String @default("member") // member, moderator
|
|
joinedAt DateTime @default(now())
|
|
|
|
room CommunicationRoom @relation(fields: [roomId], references: [id])
|
|
user User @relation(fields: [userId], references: [id])
|
|
|
|
@@unique([roomId, userId])
|
|
@@index([roomId])
|
|
@@index([userId])
|
|
@@map("communication_room_members")
|
|
}
|
|
|
|
model CommunicationMessage {
|
|
id String @id @default(cuid())
|
|
roomId String
|
|
senderId String
|
|
content String
|
|
messageType String @default("text") // text, voice, image, note_share, file
|
|
fileUrl String?
|
|
metadata String? // JSON
|
|
isRead Boolean @default(false)
|
|
createdAt DateTime @default(now())
|
|
|
|
room CommunicationRoom @relation(fields: [roomId], references: [id])
|
|
sender User @relation(fields: [senderId], references: [id])
|
|
|
|
@@index([roomId])
|
|
@@index([senderId])
|
|
@@index([createdAt])
|
|
@@map("communication_messages")
|
|
}
|
|
|
|
// ─── Counseling / Guidance System ────────────────────────────────────
|
|
|
|
model CounselingAppointment {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
studentId String
|
|
counselorId String
|
|
requestType String @default("guidance") // guidance, psychological, career, social
|
|
description String?
|
|
status String @default("requested") // requested, scheduled, completed, cancelled
|
|
scheduledAt DateTime?
|
|
duration Int @default(30) // minutes
|
|
notes String? // Counselor notes
|
|
isPrivate Boolean @default(true)
|
|
addToCalendar Boolean @default(true)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
counselor User @relation("Counselor", fields: [counselorId], references: [id])
|
|
student User @relation("CounselingStudent", fields: [studentId], references: [id])
|
|
|
|
@@index([schoolId])
|
|
@@index([counselorId])
|
|
@@index([studentId])
|
|
@@index([status])
|
|
@@index([scheduledAt])
|
|
@@map("counseling_appointments")
|
|
}
|
|
|
|
// ─── Disciplinary Committee System ───────────────────────────────────
|
|
|
|
model DisciplinaryCommittee {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
name String
|
|
description String?
|
|
isActive Boolean @default(true)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
members DisciplinaryCommitteeMember[]
|
|
cases DisciplinaryCase[]
|
|
|
|
@@index([schoolId])
|
|
@@map("disciplinary_committees")
|
|
}
|
|
|
|
model DisciplinaryCommitteeMember {
|
|
id String @id @default(cuid())
|
|
committeeId String
|
|
userId String
|
|
role String @default("member") // chair, vice_chair, member
|
|
isLead Boolean @default(false)
|
|
createdAt DateTime @default(now())
|
|
|
|
committee DisciplinaryCommittee @relation(fields: [committeeId], references: [id])
|
|
user User @relation(fields: [userId], references: [id])
|
|
|
|
@@unique([committeeId, userId])
|
|
@@index([committeeId])
|
|
@@index([userId])
|
|
@@map("disciplinary_committee_members")
|
|
}
|
|
|
|
model DisciplinaryCase {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
committeeId String
|
|
studentId String
|
|
reportedBy String
|
|
reviewedBy String?
|
|
caseType String // warning, minor_violation, major_violation, serious_offense
|
|
description String
|
|
evidence String? // JSON array of evidence URLs
|
|
status String @default("open") // open, under_review, resolved, dismissed
|
|
resolution String?
|
|
resolutionDate DateTime?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
committee DisciplinaryCommittee @relation(fields: [committeeId], references: [id])
|
|
student Student @relation(fields: [studentId], references: [id])
|
|
reporter User @relation("CaseReporter", fields: [reportedBy], references: [id])
|
|
reviewer User? @relation("CaseReviewer", fields: [reviewedBy], references: [id])
|
|
|
|
@@index([schoolId])
|
|
@@index([committeeId])
|
|
@@index([studentId])
|
|
@@index([status])
|
|
@@map("disciplinary_cases")
|
|
}
|
|
|
|
// ─── AI Test Generation ──────────────────────────────────────────────
|
|
|
|
model AITestGeneration {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
assessmentId String? // Linked assessment
|
|
studentId String? // For individual student tests
|
|
classGroupId String? // For class-wide tests
|
|
subjectId String?
|
|
topicId String? // SubjectContent ID
|
|
testType String @default("practice") // practice, mock_exam, review
|
|
difficulty String @default("medium")
|
|
questionCount Int @default(10)
|
|
questions String? // JSON array of generated questions
|
|
aiProvider String @default("pollination") // pollination, openai, anthropic
|
|
generatedAt DateTime?
|
|
isCompleted Boolean @default(false)
|
|
completedAt DateTime?
|
|
score Float? // Student's score if completed
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
assessment Assessment? @relation(fields: [assessmentId], references: [id])
|
|
student User? @relation(fields: [studentId], references: [id])
|
|
classGroup ClassGroup? @relation(fields: [classGroupId], references: [id])
|
|
subject Subject? @relation(fields: [subjectId], references: [id])
|
|
|
|
@@index([schoolId])
|
|
@@index([assessmentId])
|
|
@@index([studentId])
|
|
@@index([classGroupId])
|
|
@@map("ai_test_generations")
|
|
}
|
|
|
|
// ─── Teacher Grading Review (AI Audit) ───────────────────────────────
|
|
|
|
model TeacherGradingReview {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
assessmentId String
|
|
teacherId String
|
|
aiProvider String @default("pollination")
|
|
status String @default("pending") // pending, in_progress, completed
|
|
reviewResult String? // JSON with review findings
|
|
discrepanciesFound Int @default(0)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
assessment Assessment @relation(fields: [assessmentId], references: [id])
|
|
teacher User @relation(fields: [teacherId], references: [id])
|
|
comments GradingReviewComment[]
|
|
|
|
@@index([schoolId])
|
|
@@index([assessmentId])
|
|
@@index([teacherId])
|
|
@@index([status])
|
|
@@map("teacher_grading_reviews")
|
|
}
|
|
|
|
model GradingReviewComment {
|
|
id String @id @default(cuid())
|
|
reviewId String
|
|
userId String
|
|
resultId String? // AssessmentResult ID
|
|
comment String
|
|
suggestedGrade String?
|
|
reason String?
|
|
createdAt DateTime @default(now())
|
|
|
|
review TeacherGradingReview @relation(fields: [reviewId], references: [id])
|
|
user User @relation(fields: [userId], references: [id])
|
|
|
|
@@index([reviewId])
|
|
@@index([userId])
|
|
@@map("grading_review_comments")
|
|
}
|
|
|
|
// ─── Grading Annotations (Tablet Stylus) ────────────────────────────
|
|
|
|
model GradingAnnotation {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
assessmentId String
|
|
studentId String
|
|
resultId String? // AssessmentResult ID
|
|
teacherId String
|
|
type String @default("drawing") // drawing, text, highlight, stamp
|
|
content String? // text content or stamp symbol
|
|
positionX Float @default(0)
|
|
positionY Float @default(0)
|
|
width Float?
|
|
height Float?
|
|
color String @default("#ef4444")
|
|
strokeWidth Float @default(2)
|
|
page Int @default(1)
|
|
pathData String? // JSON: array of {x,y,pressure} points for drawing
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
assessment Assessment @relation(fields: [assessmentId], references: [id])
|
|
student Student @relation(fields: [studentId], references: [id])
|
|
result AssessmentResult? @relation(fields: [resultId], references: [id])
|
|
teacher User @relation(fields: [teacherId], references: [id])
|
|
|
|
@@index([schoolId])
|
|
@@index([assessmentId])
|
|
@@index([studentId])
|
|
@@index([teacherId])
|
|
@@index([page])
|
|
@@map("grading_annotations")
|
|
}
|
|
|
|
// ─── Exam Planning ──────────��──────────────────────────────────────
|
|
|
|
model ExamPlan {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
teacherId String
|
|
subjectId String
|
|
classGroupId String
|
|
title String
|
|
date DateTime
|
|
startTime String? // HH:mm format
|
|
endTime String? // HH:mm format
|
|
room String?
|
|
topics String? // JSON array of topic strings
|
|
weight Float @default(1.0)
|
|
status String @default("planned") // planned, confirmed, completed, cancelled
|
|
notes String?
|
|
calendarEventId String? @unique // Link to CalendarEvent
|
|
assessmentId String? @unique // Link to Assessment (after exam is created)
|
|
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])
|
|
calendarEvent CalendarEvent? @relation(fields: [calendarEventId], references: [id])
|
|
assessment Assessment? @relation(fields: [assessmentId], references: [id])
|
|
|
|
@@index([schoolId])
|
|
@@index([teacherId])
|
|
@@index([subjectId])
|
|
@@index([classGroupId])
|
|
@@index([date])
|
|
@@index([status])
|
|
@@map("exam_plans")
|
|
}
|
|
|
|
// ─── Student Study Planner ──────────────────────────────────────────
|
|
|
|
model StudyPlan {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
studentId String
|
|
title String
|
|
description String?
|
|
subjectId String?
|
|
subjectName String? // Denormalized subject name for display
|
|
dayOfWeek Int // 0=Sunday, 1=Monday, ..., 6=Saturday
|
|
startTime String // HH:mm format
|
|
duration Int // Duration in minutes
|
|
isRecurring Boolean @default(true)
|
|
specificDate DateTime? // For one-time study sessions
|
|
priority String @default("medium") // low, medium, high
|
|
status String @default("active") // active, completed, paused, cancelled
|
|
color String? // Custom color for the plan
|
|
notes String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
student Student @relation(fields: [studentId], references: [id])
|
|
subject Subject? @relation(fields: [subjectId], references: [id])
|
|
sessions StudySession[]
|
|
|
|
@@index([schoolId])
|
|
@@index([studentId])
|
|
@@index([dayOfWeek])
|
|
@@index([status])
|
|
@@map("study_plans")
|
|
}
|
|
|
|
model StudySession {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
studentId String
|
|
studyPlanId String? // Optional link to a StudyPlan
|
|
subjectId String?
|
|
subjectName String? // Denormalized subject name
|
|
startTime DateTime
|
|
endTime DateTime?
|
|
duration Int @default(0) // Actual duration in minutes
|
|
plannedDuration Int? // Planned duration in minutes
|
|
type String @default("pomodoro") // pomodoro, free, guided
|
|
pomodorosCompleted Int @default(0)
|
|
pomodoroLength Int @default(25) // Minutes per pomodoro
|
|
breakLength Int @default(5) // Minutes per break
|
|
status String @default("in_progress") // in_progress, completed, abandoned
|
|
focusScore Int? // 1-100 self-reported focus score
|
|
notes String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
student Student @relation(fields: [studentId], references: [id])
|
|
studyPlan StudyPlan? @relation(fields: [studyPlanId], references: [id])
|
|
subject Subject? @relation(fields: [subjectId], references: [id])
|
|
|
|
@@index([schoolId])
|
|
@@index([studentId])
|
|
@@index([startTime])
|
|
@@index([status])
|
|
@@map("study_sessions")
|
|
}
|
|
|
|
// ─── School Library ──────────────────────────────────────────────────
|
|
|
|
model LibraryBook {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
title String
|
|
author String
|
|
isbn String?
|
|
publisher String?
|
|
publishYear Int?
|
|
category String // Fiction, Non-Fiction, Science, History, etc.
|
|
readingLevel String? // elementary, middle, high, advanced
|
|
language String @default("de")
|
|
description String?
|
|
coverGradient String? // JSON: {from, to} gradient colors for placeholder
|
|
totalCopies Int @default(1)
|
|
availableCopies Int @default(1)
|
|
location String? // Shelf/room location
|
|
tags String? // JSON array of tag strings
|
|
isDemo Boolean @default(false)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
checkouts BookCheckout[]
|
|
reservations BookReservation[]
|
|
|
|
@@index([schoolId])
|
|
@@index([category])
|
|
@@index([readingLevel])
|
|
@@index([author])
|
|
@@map("library_books")
|
|
}
|
|
|
|
model BookCheckout {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
bookId String
|
|
studentId String
|
|
checkedOutBy String // userId of the person who processed the checkout
|
|
checkoutDate DateTime @default(now())
|
|
dueDate DateTime
|
|
returnDate DateTime?
|
|
condition String @default("good") // excellent, good, fair, poor
|
|
returnCondition String? // Condition upon return
|
|
renewalCount Int @default(0)
|
|
status String @default("active") // active, returned, overdue, lost
|
|
fineAmount Float @default(0)
|
|
finePaid Boolean @default(false)
|
|
notes String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
book LibraryBook @relation(fields: [bookId], references: [id])
|
|
student Student @relation(fields: [studentId], references: [id])
|
|
checkedOutByUser User @relation(fields: [checkedOutBy], references: [id])
|
|
|
|
@@index([schoolId])
|
|
@@index([bookId])
|
|
@@index([studentId])
|
|
@@index([status])
|
|
@@index([dueDate])
|
|
@@map("book_checkouts")
|
|
}
|
|
|
|
model BookReservation {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
bookId String
|
|
studentId String
|
|
userId String // The user who made the reservation (could be parent)
|
|
queuePosition Int @default(1)
|
|
status String @default("waiting") // waiting, notified, fulfilled, cancelled
|
|
notifiedAt DateTime?
|
|
fulfilledAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
book LibraryBook @relation(fields: [bookId], references: [id])
|
|
student Student @relation(fields: [studentId], references: [id])
|
|
user User @relation(fields: [userId], references: [id])
|
|
|
|
@@index([schoolId])
|
|
@@index([bookId])
|
|
@@index([studentId])
|
|
@@index([status])
|
|
@@map("book_reservations")
|
|
}
|
|
|
|
// ─── Seating Charts ──────────────────────────────────────────────────
|
|
|
|
model SeatingChart {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
classGroupId String
|
|
teacherId String
|
|
name String
|
|
layoutType String @default("rows") // rows, groups, u-shape, circle, custom
|
|
rows Int @default(5)
|
|
columns Int @default(5)
|
|
gap Int @default(2) // Gap between desks (in grid units)
|
|
arrangement String? // JSON: [{ studentId, row, col }] — student seat assignments
|
|
showDoor Boolean @default(true)
|
|
showWindows Boolean @default(true)
|
|
doorPosition String @default("left") // left, right, back
|
|
windowPosition String @default("right") // left, right, both
|
|
isTemplate Boolean @default(false)
|
|
isDefault Boolean @default(false)
|
|
notes String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
classGroup ClassGroup @relation(fields: [classGroupId], references: [id])
|
|
teacher User @relation(fields: [teacherId], references: [id])
|
|
|
|
@@index([schoolId])
|
|
@@index([classGroupId])
|
|
@@index([teacherId])
|
|
@@map("seating_charts")
|
|
}
|
|
|
|
model DataImportJob {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
userId String
|
|
type String // STUDENT, TEACHER, GRADE, ATTENDANCE, COMPETENCY
|
|
fileName String
|
|
fileSize Int
|
|
status String @default("pending") // pending, processing, completed, failed
|
|
totalRows Int @default(0)
|
|
successRows Int @default(0)
|
|
errorRows Int @default(0)
|
|
errors String? // JSON string of error details
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
user User @relation(fields: [userId], references: [id])
|
|
|
|
@@index([schoolId])
|
|
@@index([userId])
|
|
@@map("data_import_jobs")
|
|
}
|
|
|
|
model DataExportJob {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
userId String
|
|
type String // STUDENT, GRADE, ATTENDANCE, COMPETENCY, REPORT
|
|
format String // CSV, JSON
|
|
filters String? // JSON string of filter parameters
|
|
fileName String?
|
|
fileSize Int @default(0)
|
|
status String @default("pending") // pending, processing, completed, failed
|
|
fileData String? // Base64 encoded file data
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
user User @relation(fields: [userId], references: [id])
|
|
|
|
@@index([schoolId])
|
|
@@index([userId])
|
|
@@map("data_export_jobs")
|
|
}
|
|
|
|
model WellnessCheckin {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
studentId String
|
|
date DateTime
|
|
mood Int // 1-5
|
|
sleepHours Float?
|
|
sleepQuality Int? // 1-5
|
|
stressLevel Int? // 1-10
|
|
activityType String?
|
|
activityMinutes Int?
|
|
mealsCount Int?
|
|
waterGlasses Int?
|
|
gratitudeEntry String?
|
|
notes String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
student Student @relation(fields: [studentId], references: [id])
|
|
|
|
@@index([schoolId])
|
|
@@index([studentId])
|
|
@@index([date])
|
|
@@map("wellness_checkins")
|
|
}
|
|
|
|
model WellnessScore {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
studentId String
|
|
date DateTime
|
|
overallScore Float // 0-100
|
|
physicalScore Float? // 0-100
|
|
mentalScore Float? // 0-100
|
|
socialScore Float? // 0-100
|
|
academicScore Float? // 0-100
|
|
createdAt DateTime @default(now())
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
student Student @relation(fields: [studentId], references: [id])
|
|
|
|
@@index([schoolId])
|
|
@@index([studentId])
|
|
@@index([date])
|
|
@@map("wellness_scores")
|
|
}
|
|
|
|
// ─── Student Career & Guidance ───────────────────────────────────────
|
|
|
|
model CareerProfile {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
studentId String @unique
|
|
interests String? // JSON: ["technology", "health", "business"]
|
|
strengths String? // JSON: ["mathematics", "communication"]
|
|
careerCluster String? // Technology, Health, Business, Arts, Science, Social
|
|
desiredCareer String?
|
|
educationPath String? // Hauptschule, Realschule, Gymnasium, Berufsschule
|
|
workExperiences String? // JSON: [{ company, role, startDate, endDate }]
|
|
volunteerExps String? // JSON: [{ organization, role, startDate, endDate }]
|
|
certifications String? // JSON: [{ name, date, issuer }]
|
|
documents String? // JSON: [{ name, url, type }]
|
|
notes String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
student Student @relation(fields: [studentId], references: [id])
|
|
goals CareerGoal[]
|
|
appointments CareerAppointment[]
|
|
|
|
@@index([schoolId])
|
|
@@index([studentId])
|
|
@@index([careerCluster])
|
|
@@map("career_profiles")
|
|
}
|
|
|
|
model CareerGoal {
|
|
id String @id @default(cuid())
|
|
profileId String
|
|
title String
|
|
description String?
|
|
category String // short_term, long_term, academic, professional
|
|
targetDate DateTime?
|
|
progress Int @default(0) // 0-100
|
|
status String @default("active") // active, completed, abandoned
|
|
milestones String? // JSON: [{ title, completed, date }]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
profile CareerProfile @relation(fields: [profileId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([profileId])
|
|
@@index([category])
|
|
@@index([status])
|
|
@@map("career_goals")
|
|
}
|
|
|
|
model CareerAppointment {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
profileId String
|
|
counselorId String
|
|
date DateTime
|
|
duration Int @default(30) // minutes
|
|
type String @default("guidance") // guidance, career, academic, college
|
|
status String @default("scheduled") // scheduled, completed, cancelled
|
|
notes String?
|
|
actionItems String? // JSON: [{ item, completed }]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
profile CareerProfile @relation(fields: [profileId], references: [id], onDelete: Cascade)
|
|
counselor User @relation(fields: [counselorId], references: [id])
|
|
|
|
@@index([schoolId])
|
|
@@index([profileId])
|
|
@@index([counselorId])
|
|
@@index([date])
|
|
@@index([status])
|
|
@@map("career_appointments")
|
|
}
|
|
|
|
model SubstituteTeacher {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
userId String? @unique // optional link to User account
|
|
firstName String
|
|
lastName String
|
|
email String?
|
|
phone String?
|
|
qualifications String? // JSON: ["Mathematics", "Physics", "German"]
|
|
subjects String? // JSON: subject IDs they can teach
|
|
gradeLevels String? // JSON: ["1-4", "5-6", "7-10"]
|
|
availability String? // JSON: { "monday": ["08:00-12:00"], "tuesday": [...] }
|
|
maxDaysPerWeek Int @default(5)
|
|
rating Float @default(0) // 0-5 average rating
|
|
totalAssignments Int @default(0)
|
|
notes String?
|
|
isActive Boolean @default(true)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
deletedAt DateTime?
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
user User? @relation(fields: [userId], references: [id])
|
|
assignments SubstitutionAssignment[]
|
|
|
|
@@index([schoolId])
|
|
@@index([userId])
|
|
@@map("substitute_teachers")
|
|
}
|
|
|
|
model TeacherAbsence {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
teacherId String
|
|
type String // SICK, PERSONAL, TRAINING, OTHER
|
|
startDate DateTime
|
|
endDate DateTime
|
|
reason String?
|
|
status String @default("reported") // reported, covered, cancelled
|
|
notes String?
|
|
approvalStatus String @default("pending") // pending, approved, rejected
|
|
approvedBy String?
|
|
approvedAt DateTime?
|
|
privateAdminNotes String?
|
|
documentUrl String?
|
|
calendarEventId String? @unique
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
teacher User @relation(fields: [teacherId], references: [id])
|
|
approver User? @relation("TeacherAbsenceApprover", fields: [approvedBy], references: [id])
|
|
calendarEvent CalendarEvent? @relation("TeacherAbsenceCalendar", fields: [calendarEventId], references: [id])
|
|
assignments SubstitutionAssignment[]
|
|
|
|
@@index([schoolId])
|
|
@@index([teacherId])
|
|
@@index([startDate])
|
|
@@index([approvalStatus])
|
|
@@map("teacher_absences")
|
|
}
|
|
|
|
model SubstitutionAssignment {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
absenceId String
|
|
substituteId String
|
|
classGroupId String?
|
|
subjectId String?
|
|
date DateTime
|
|
period Int? // lesson period number
|
|
room String?
|
|
status String @default("pending") // pending, confirmed, completed, cancelled
|
|
notes String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
absence TeacherAbsence @relation(fields: [absenceId], references: [id])
|
|
substitute SubstituteTeacher @relation(fields: [substituteId], references: [id])
|
|
classGroup ClassGroup? @relation(fields: [classGroupId], references: [id])
|
|
subject Subject? @relation(fields: [subjectId], references: [id])
|
|
|
|
@@index([schoolId])
|
|
@@index([absenceId])
|
|
@@index([substituteId])
|
|
@@index([date])
|
|
@@map("substitution_assignments")
|
|
}
|
|
|
|
// ─── Grade Analytics & Reporting ──────────────────────────────────
|
|
|
|
model GradeReport {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
generatedBy String
|
|
title String
|
|
type String @default("custom") // quarterly, semester, annual, custom
|
|
dateRange String // JSON: { from, to }
|
|
classIds String? // JSON: array of class IDs
|
|
subjectIds String? // JSON: array of subject IDs
|
|
metrics String? // JSON: array of selected metrics
|
|
status String @default("pending") // pending, completed, failed
|
|
fileData String? // Base64 encoded or JSON report data
|
|
createdAt DateTime @default(now())
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
generator User @relation("GradeReportGenerator", fields: [generatedBy], references: [id])
|
|
|
|
@@index([schoolId])
|
|
@@index([generatedBy])
|
|
}
|
|
|
|
// ─── Module L: Legal & Data Protection Compliance ──────────────────
|
|
|
|
model DataRetentionPolicy {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
dataCategory String // LEARNING_PROGRESS, ASSESSMENT_RESULT, FINAL_REPORT, DISCIPLINARY_RECORD, EXAM_INCIDENT, MESSAGE, AI_TUTOR_LOG, NOTEBOOK_PAGE, SIGNAGE_LOG, TEACHER_AUDIT_LOG
|
|
retentionPeriodMonths Int? // nullable to force DPO to fill in legally-mandated categories
|
|
legalBasisNote String? // DPO notes on state-specific retention rule
|
|
isDefault Boolean @default(false) // false = legally mandated; true = optional product data
|
|
reviewedByUserId String? // DPO approval
|
|
reviewedAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
reviewedByUser User? @relation("DataRetentionPolicyReviewer", fields: [reviewedByUserId], references: [id])
|
|
|
|
@@index([schoolId])
|
|
@@index([dataCategory])
|
|
}
|
|
|
|
model DpiaRecord {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
moduleScope String // AI_TUTOR, EXAM_MODE, WELLNESS_CHECKS, SIGNAGE_EMERGENCY, MESSAGING, LEARNING_ANALYTICS
|
|
dataProcessingSummary String // what's processed, for how long, who sees it
|
|
completedAt DateTime
|
|
approvedByUserId String? // DPO approval
|
|
documentUrl String? // uploaded DPIA PDF
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
approvedByUser User? @relation("DpiaRecordApprover", fields: [approvedByUserId], references: [id])
|
|
|
|
@@index([schoolId])
|
|
@@index([moduleScope])
|
|
@@unique([schoolId, moduleScope])
|
|
}
|
|
|
|
model ComplianceConsultation {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
consultationType String // ELTERNBEIRAT, SCHULKONFERENZ, PERSONALRAT
|
|
moduleScope String? // specific module if Personalrat for monitoring-capable features
|
|
acknowledgedByUserId String
|
|
acknowledgedAt DateTime
|
|
notes String? // what was consulted/approved
|
|
createdAt DateTime @default(now())
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
acknowledgedByUser User @relation("ComplianceConsultationAcknowledger", fields: [acknowledgedByUserId], references: [id])
|
|
|
|
@@index([schoolId])
|
|
@@index([consultationType])
|
|
}
|
|
|
|
model SchoolComplianceStatus {
|
|
id String @id @default(cuid())
|
|
schoolId String @unique
|
|
bundesland String? // BERLIN, BREMEN, HAMBURG, etc. (16 states)
|
|
onboardingCompleted Boolean @default(false)
|
|
dpoDpoContactEmail String?
|
|
schultragerContactEmail String?
|
|
moduleGates String? // JSON: { "AI_TUTOR": { isApprovedForUse: bool, diaCompleted: bool }, ... }
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
}
|
|
|
|
model DeletionFlag {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
entityType String // Student, Message, Notebook, etc.
|
|
entityId String
|
|
dataCategory String // retention category reference
|
|
flaggedAt DateTime @default(now())
|
|
approvedByUserId String?
|
|
approvedAt DateTime?
|
|
deletedAt DateTime?
|
|
reason String // "retention period expired", "erasure request", etc.
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
approvedByUser User? @relation("DeletionFlagApprover", fields: [approvedByUserId], references: [id])
|
|
|
|
@@index([schoolId])
|
|
@@index([entityType])
|
|
@@index([flaggedAt])
|
|
}
|
|
|
|
// ─── Module B: Emergency Signage System ─────────────────────────────
|
|
|
|
// ─── Module F: Exam Mode / Digital Test Environment ──────────────────
|
|
|
|
model ExamSession {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
assessmentId String
|
|
studentId String
|
|
startTime DateTime
|
|
endTime DateTime?
|
|
submittedAt DateTime?
|
|
status String @default("not_started") // not_started, in_progress, submitted, graded
|
|
score Float?
|
|
totalPoints Float?
|
|
lockdownEnabled Boolean @default(true) // Kiosk mode lockdown
|
|
cameraMonitor Boolean @default(false) // Enable face monitoring
|
|
screenShare Boolean @default(false) // Allow screen sharing for proctoring
|
|
environmentNotes String? // JSON: { browser, os, resolution, etc }
|
|
suspiciousActivity String? // JSON array of incidents
|
|
createdAt DateTime @default(now())
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
assessment Assessment @relation(fields: [assessmentId], references: [id])
|
|
student Student @relation(fields: [studentId], references: [id])
|
|
answers ExamAnswer[]
|
|
events ExamEvent[]
|
|
warnings ExamWarning[]
|
|
|
|
@@index([schoolId])
|
|
@@index([assessmentId])
|
|
@@index([studentId])
|
|
@@index([status])
|
|
@@map("exam_sessions")
|
|
}
|
|
|
|
model ExamAnswer {
|
|
id String @id @default(cuid())
|
|
examSessionId String
|
|
questionId String
|
|
answer String? // JSON: selected choice, free text, etc
|
|
submittedAt DateTime @default(now())
|
|
isCorrect Boolean?
|
|
pointsAwarded Float?
|
|
reviewedAt DateTime?
|
|
|
|
examSession ExamSession @relation(fields: [examSessionId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([examSessionId])
|
|
@@index([questionId])
|
|
@@map("exam_answers")
|
|
}
|
|
|
|
model ExamEvent {
|
|
id String @id @default(cuid())
|
|
examSessionId String
|
|
eventType String // tab_switch, copy_paste, window_blur, camera_detected_none, suspicious_movement, time_warning
|
|
severity String @default("info") // info, warning, critical
|
|
description String?
|
|
timestamp DateTime @default(now())
|
|
metadata String? // JSON: additional context
|
|
|
|
examSession ExamSession @relation(fields: [examSessionId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([examSessionId])
|
|
@@index([eventType])
|
|
@@map("exam_events")
|
|
}
|
|
|
|
model ExamWarning {
|
|
id String @id @default(cuid())
|
|
examSessionId String
|
|
studentId String
|
|
warningType String // time_low, behavior_suspicious, multiple_tabs, camera_issue
|
|
message String
|
|
acknowledged Boolean @default(false)
|
|
acknowledgedAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
|
|
examSession ExamSession @relation(fields: [examSessionId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([examSessionId])
|
|
@@index([studentId])
|
|
@@map("exam_warnings")
|
|
}
|
|
|
|
// ─── Module H: Centralized Notifications Hub ───────────────────────
|
|
|
|
// ─── Module J: Customizable Dashboard Widgets ──────────────────────
|
|
|
|
model DashboardWidget {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
userId String
|
|
widgetType String // grade_summary, attendance_trend, upcoming_assessments, class_schedule, wellness_status, recent_messages, announcements
|
|
title String? // Custom title override
|
|
position Int // Grid position (0-11)
|
|
size String @default("medium") // small, medium, large, full_width
|
|
isVisible Boolean @default(true)
|
|
config String? // JSON: widget-specific settings { colors, dateRange, filters, etc }
|
|
dataSource String? // API endpoint or computed field reference
|
|
refreshInterval Int @default(300) // seconds (0 = manual)
|
|
lastRefresh DateTime?
|
|
cacheData String? // JSON cached widget data
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
user User @relation(fields: [userId], references: [id])
|
|
|
|
@@index([schoolId])
|
|
@@index([userId])
|
|
@@index([widgetType])
|
|
@@unique([userId, position])
|
|
@@map("dashboard_widgets")
|
|
}
|
|
|
|
model DashboardLayout {
|
|
id String @id @default(cuid())
|
|
schoolId String
|
|
userId String
|
|
name String // "Default", "Teacher View", "Student View", etc
|
|
gridColumns Int @default(4) // Grid layout columns
|
|
isDefault Boolean @default(false)
|
|
widgets String // JSON: array of widget positions
|
|
theme String? // Dark, light, custom
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
school School @relation(fields: [schoolId], references: [id])
|
|
user User @relation(fields: [userId], references: [id])
|
|
|
|
@@index([schoolId])
|
|
@@index([userId])
|
|
@@map("dashboard_layouts")
|
|
}
|
|
|