Install LOC guardrails (check-loc.sh, architecture.md, pre-commit hook) and split all 44 files exceeding 500 LOC into domain-focused modules: - consent-service (Go): models, handlers, services, database splits - backend-core (Python): security_api, rbac_api, pdf_service, auth splits - admin-core (TypeScript): 5 page.tsx + sidebar extractions - pitch-deck (TypeScript): 6 slides, 3 UI components, engine.ts splits - voice-service (Python): enhanced_task_orchestrator split Result: 0 violations, 36 exempted (pipeline, tests, pure-data files). Go build verified clean. No behavior changes — pure structural splits. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
383 lines
18 KiB
Go
383 lines
18 KiB
Go
package models
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// ========================================
|
|
// Stundenplan / Timetable
|
|
// ========================================
|
|
|
|
// TimetableSlot represents a time slot in the timetable
|
|
type TimetableSlot struct {
|
|
ID uuid.UUID `json:"id" db:"id"`
|
|
SchoolID uuid.UUID `json:"school_id" db:"school_id"`
|
|
SlotNumber int `json:"slot_number" db:"slot_number"` // 1, 2, 3... (Stunde)
|
|
StartTime string `json:"start_time" db:"start_time"` // "08:00"
|
|
EndTime string `json:"end_time" db:"end_time"` // "08:45"
|
|
IsBreak bool `json:"is_break" db:"is_break"` // Pause
|
|
Name *string `json:"name,omitempty" db:"name"` // e.g., "1. Stunde", "Große Pause"
|
|
}
|
|
|
|
// TimetableEntry represents a single lesson in the timetable
|
|
type TimetableEntry struct {
|
|
ID uuid.UUID `json:"id" db:"id"`
|
|
SchoolYearID uuid.UUID `json:"school_year_id" db:"school_year_id"`
|
|
ClassID uuid.UUID `json:"class_id" db:"class_id"`
|
|
SubjectID uuid.UUID `json:"subject_id" db:"subject_id"`
|
|
TeacherID uuid.UUID `json:"teacher_id" db:"teacher_id"`
|
|
SlotID uuid.UUID `json:"slot_id" db:"slot_id"`
|
|
DayOfWeek int `json:"day_of_week" db:"day_of_week"` // 1=Monday, 5=Friday
|
|
Room *string `json:"room,omitempty" db:"room"`
|
|
ValidFrom time.Time `json:"valid_from" db:"valid_from"`
|
|
ValidUntil *time.Time `json:"valid_until,omitempty" db:"valid_until"`
|
|
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
|
}
|
|
|
|
// TimetableSubstitution represents a substitution/replacement lesson
|
|
type TimetableSubstitution struct {
|
|
ID uuid.UUID `json:"id" db:"id"`
|
|
OriginalEntryID uuid.UUID `json:"original_entry_id" db:"original_entry_id"`
|
|
Date time.Time `json:"date" db:"date"`
|
|
SubstituteTeacherID *uuid.UUID `json:"substitute_teacher_id,omitempty" db:"substitute_teacher_id"`
|
|
SubstituteSubjectID *uuid.UUID `json:"substitute_subject_id,omitempty" db:"substitute_subject_id"`
|
|
Room *string `json:"room,omitempty" db:"room"`
|
|
Type string `json:"type" db:"type"` // 'substitution', 'cancelled', 'room_change', 'supervision'
|
|
Note *string `json:"note,omitempty" db:"note"`
|
|
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
|
CreatedBy uuid.UUID `json:"created_by" db:"created_by"`
|
|
}
|
|
|
|
// ========================================
|
|
// Abwesenheit / Attendance
|
|
// ========================================
|
|
|
|
// AttendanceRecord represents a student's attendance for a specific lesson
|
|
type AttendanceRecord struct {
|
|
ID uuid.UUID `json:"id" db:"id"`
|
|
StudentID uuid.UUID `json:"student_id" db:"student_id"`
|
|
TimetableEntryID *uuid.UUID `json:"timetable_entry_id,omitempty" db:"timetable_entry_id"`
|
|
Date time.Time `json:"date" db:"date"`
|
|
SlotID uuid.UUID `json:"slot_id" db:"slot_id"`
|
|
Status string `json:"status" db:"status"` // AttendanceStatus constants
|
|
RecordedBy uuid.UUID `json:"recorded_by" db:"recorded_by"`
|
|
Note *string `json:"note,omitempty" db:"note"`
|
|
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
|
}
|
|
|
|
// AbsenceReport represents a full absence report (one or more days)
|
|
type AbsenceReport struct {
|
|
ID uuid.UUID `json:"id" db:"id"`
|
|
StudentID uuid.UUID `json:"student_id" db:"student_id"`
|
|
StartDate time.Time `json:"start_date" db:"start_date"`
|
|
EndDate time.Time `json:"end_date" db:"end_date"`
|
|
Reason *string `json:"reason,omitempty" db:"reason"`
|
|
ReasonCategory string `json:"reason_category" db:"reason_category"`
|
|
Status string `json:"status" db:"status"`
|
|
ReportedBy uuid.UUID `json:"reported_by" db:"reported_by"`
|
|
ReportedAt time.Time `json:"reported_at" db:"reported_at"`
|
|
ConfirmedBy *uuid.UUID `json:"confirmed_by,omitempty" db:"confirmed_by"`
|
|
ConfirmedAt *time.Time `json:"confirmed_at,omitempty" db:"confirmed_at"`
|
|
MedicalCertificate bool `json:"medical_certificate" db:"medical_certificate"`
|
|
CertificateUploaded bool `json:"certificate_uploaded" db:"certificate_uploaded"`
|
|
MatrixNotificationSent bool `json:"matrix_notification_sent" db:"matrix_notification_sent"`
|
|
EmailNotificationSent bool `json:"email_notification_sent" db:"email_notification_sent"`
|
|
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
|
}
|
|
|
|
// AbsenceNotification tracks notifications sent to parents about absences
|
|
type AbsenceNotification struct {
|
|
ID uuid.UUID `json:"id" db:"id"`
|
|
AttendanceRecordID uuid.UUID `json:"attendance_record_id" db:"attendance_record_id"`
|
|
ParentID uuid.UUID `json:"parent_id" db:"parent_id"`
|
|
Channel string `json:"channel" db:"channel"`
|
|
MessageContent string `json:"message_content" db:"message_content"`
|
|
SentAt *time.Time `json:"sent_at,omitempty" db:"sent_at"`
|
|
ReadAt *time.Time `json:"read_at,omitempty" db:"read_at"`
|
|
ResponseReceived bool `json:"response_received" db:"response_received"`
|
|
ResponseContent *string `json:"response_content,omitempty" db:"response_content"`
|
|
ResponseAt *time.Time `json:"response_at,omitempty" db:"response_at"`
|
|
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
|
}
|
|
|
|
// ========================================
|
|
// Notenspiegel / Grades
|
|
// ========================================
|
|
|
|
// GradeScale represents the grading scale used
|
|
type GradeScale struct {
|
|
ID uuid.UUID `json:"id" db:"id"`
|
|
SchoolID uuid.UUID `json:"school_id" db:"school_id"`
|
|
Name string `json:"name" db:"name"`
|
|
MinValue float64 `json:"min_value" db:"min_value"`
|
|
MaxValue float64 `json:"max_value" db:"max_value"`
|
|
PassingValue float64 `json:"passing_value" db:"passing_value"`
|
|
IsAscending bool `json:"is_ascending" db:"is_ascending"`
|
|
IsDefault bool `json:"is_default" db:"is_default"`
|
|
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
|
}
|
|
|
|
// Grade represents a single grade for a student
|
|
type Grade struct {
|
|
ID uuid.UUID `json:"id" db:"id"`
|
|
StudentID uuid.UUID `json:"student_id" db:"student_id"`
|
|
SubjectID uuid.UUID `json:"subject_id" db:"subject_id"`
|
|
TeacherID uuid.UUID `json:"teacher_id" db:"teacher_id"`
|
|
SchoolYearID uuid.UUID `json:"school_year_id" db:"school_year_id"`
|
|
GradeScaleID uuid.UUID `json:"grade_scale_id" db:"grade_scale_id"`
|
|
Type string `json:"type" db:"type"`
|
|
Value float64 `json:"value" db:"value"`
|
|
Weight float64 `json:"weight" db:"weight"`
|
|
Date time.Time `json:"date" db:"date"`
|
|
Title *string `json:"title,omitempty" db:"title"`
|
|
Description *string `json:"description,omitempty" db:"description"`
|
|
IsVisible bool `json:"is_visible" db:"is_visible"`
|
|
Semester int `json:"semester" db:"semester"`
|
|
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
|
}
|
|
|
|
// GradeComment represents a teacher comment on a student's grade
|
|
type GradeComment struct {
|
|
ID uuid.UUID `json:"id" db:"id"`
|
|
GradeID uuid.UUID `json:"grade_id" db:"grade_id"`
|
|
TeacherID uuid.UUID `json:"teacher_id" db:"teacher_id"`
|
|
Comment string `json:"comment" db:"comment"`
|
|
IsPrivate bool `json:"is_private" db:"is_private"`
|
|
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
|
}
|
|
|
|
// ========================================
|
|
// Klassenbuch, Meetings, Communication
|
|
// ========================================
|
|
|
|
// ClassDiaryEntry represents an entry in the digital class diary
|
|
type ClassDiaryEntry struct {
|
|
ID uuid.UUID `json:"id" db:"id"`
|
|
ClassID uuid.UUID `json:"class_id" db:"class_id"`
|
|
Date time.Time `json:"date" db:"date"`
|
|
SlotID uuid.UUID `json:"slot_id" db:"slot_id"`
|
|
SubjectID uuid.UUID `json:"subject_id" db:"subject_id"`
|
|
TeacherID uuid.UUID `json:"teacher_id" db:"teacher_id"`
|
|
Topic *string `json:"topic,omitempty" db:"topic"`
|
|
Homework *string `json:"homework,omitempty" db:"homework"`
|
|
HomeworkDueDate *time.Time `json:"homework_due_date,omitempty" db:"homework_due_date"`
|
|
Materials *string `json:"materials,omitempty" db:"materials"`
|
|
Notes *string `json:"notes,omitempty" db:"notes"`
|
|
IsCancelled bool `json:"is_cancelled" db:"is_cancelled"`
|
|
CancellationReason *string `json:"cancellation_reason,omitempty" db:"cancellation_reason"`
|
|
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
|
}
|
|
|
|
// ParentMeetingSlot represents available time slots for parent meetings
|
|
type ParentMeetingSlot struct {
|
|
ID uuid.UUID `json:"id" db:"id"`
|
|
TeacherID uuid.UUID `json:"teacher_id" db:"teacher_id"`
|
|
Date time.Time `json:"date" db:"date"`
|
|
StartTime string `json:"start_time" db:"start_time"`
|
|
EndTime string `json:"end_time" db:"end_time"`
|
|
Location *string `json:"location,omitempty" db:"location"`
|
|
IsOnline bool `json:"is_online" db:"is_online"`
|
|
MeetingLink *string `json:"meeting_link,omitempty" db:"meeting_link"`
|
|
IsBooked bool `json:"is_booked" db:"is_booked"`
|
|
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
|
}
|
|
|
|
// ParentMeeting represents a booked parent-teacher meeting
|
|
type ParentMeeting struct {
|
|
ID uuid.UUID `json:"id" db:"id"`
|
|
SlotID uuid.UUID `json:"slot_id" db:"slot_id"`
|
|
ParentID uuid.UUID `json:"parent_id" db:"parent_id"`
|
|
StudentID uuid.UUID `json:"student_id" db:"student_id"`
|
|
Topic *string `json:"topic,omitempty" db:"topic"`
|
|
Notes *string `json:"notes,omitempty" db:"notes"`
|
|
Status string `json:"status" db:"status"`
|
|
CancelledAt *time.Time `json:"cancelled_at,omitempty" db:"cancelled_at"`
|
|
CancelledBy *uuid.UUID `json:"cancelled_by,omitempty" db:"cancelled_by"`
|
|
CancelReason *string `json:"cancel_reason,omitempty" db:"cancel_reason"`
|
|
CompletedAt *time.Time `json:"completed_at,omitempty" db:"completed_at"`
|
|
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
|
}
|
|
|
|
// MatrixRoom tracks Matrix rooms created for school communication
|
|
type MatrixRoom struct {
|
|
ID uuid.UUID `json:"id" db:"id"`
|
|
SchoolID uuid.UUID `json:"school_id" db:"school_id"`
|
|
MatrixRoomID string `json:"matrix_room_id" db:"matrix_room_id"`
|
|
Type string `json:"type" db:"type"`
|
|
ClassID *uuid.UUID `json:"class_id,omitempty" db:"class_id"`
|
|
StudentID *uuid.UUID `json:"student_id,omitempty" db:"student_id"`
|
|
Name string `json:"name" db:"name"`
|
|
IsEncrypted bool `json:"is_encrypted" db:"is_encrypted"`
|
|
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
|
}
|
|
|
|
// MatrixRoomMember tracks membership in Matrix rooms
|
|
type MatrixRoomMember struct {
|
|
ID uuid.UUID `json:"id" db:"id"`
|
|
MatrixRoomID uuid.UUID `json:"matrix_room_id" db:"matrix_room_id"`
|
|
MatrixUserID string `json:"matrix_user_id" db:"matrix_user_id"`
|
|
UserID *uuid.UUID `json:"user_id,omitempty" db:"user_id"`
|
|
PowerLevel int `json:"power_level" db:"power_level"`
|
|
CanWrite bool `json:"can_write" db:"can_write"`
|
|
JoinedAt time.Time `json:"joined_at" db:"joined_at"`
|
|
LeftAt *time.Time `json:"left_at,omitempty" db:"left_at"`
|
|
}
|
|
|
|
// ParentOnboardingToken for QR-code based parent onboarding
|
|
type ParentOnboardingToken struct {
|
|
ID uuid.UUID `json:"id" db:"id"`
|
|
SchoolID uuid.UUID `json:"school_id" db:"school_id"`
|
|
ClassID uuid.UUID `json:"class_id" db:"class_id"`
|
|
StudentID uuid.UUID `json:"student_id" db:"student_id"`
|
|
Token string `json:"token" db:"token"`
|
|
Role string `json:"role" db:"role"`
|
|
ExpiresAt time.Time `json:"expires_at" db:"expires_at"`
|
|
UsedAt *time.Time `json:"used_at,omitempty" db:"used_at"`
|
|
UsedByUserID *uuid.UUID `json:"used_by_user_id,omitempty" db:"used_by_user_id"`
|
|
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
|
CreatedBy uuid.UUID `json:"created_by" db:"created_by"`
|
|
}
|
|
|
|
// ========================================
|
|
// Schulverwaltung DTOs
|
|
// ========================================
|
|
|
|
// CreateSchoolRequest for creating a new school
|
|
type CreateSchoolRequest struct {
|
|
Name string `json:"name" binding:"required"`
|
|
ShortName *string `json:"short_name"`
|
|
Type string `json:"type" binding:"required"`
|
|
Address *string `json:"address"`
|
|
City *string `json:"city"`
|
|
PostalCode *string `json:"postal_code"`
|
|
State *string `json:"state"`
|
|
Phone *string `json:"phone"`
|
|
Email *string `json:"email"`
|
|
Website *string `json:"website"`
|
|
}
|
|
|
|
// CreateClassRequest for creating a new class
|
|
type CreateClassRequest struct {
|
|
SchoolYearID string `json:"school_year_id" binding:"required"`
|
|
Name string `json:"name" binding:"required"`
|
|
Grade int `json:"grade" binding:"required"`
|
|
Section *string `json:"section"`
|
|
Room *string `json:"room"`
|
|
}
|
|
|
|
// CreateStudentRequest for creating a new student
|
|
type CreateStudentRequest struct {
|
|
ClassID string `json:"class_id" binding:"required"`
|
|
StudentNumber *string `json:"student_number"`
|
|
FirstName string `json:"first_name" binding:"required"`
|
|
LastName string `json:"last_name" binding:"required"`
|
|
DateOfBirth *string `json:"date_of_birth"` // ISO 8601
|
|
Gender *string `json:"gender"`
|
|
}
|
|
|
|
// RecordAttendanceRequest for recording attendance
|
|
type RecordAttendanceRequest struct {
|
|
StudentID string `json:"student_id" binding:"required"`
|
|
Date string `json:"date" binding:"required"` // ISO 8601
|
|
SlotID string `json:"slot_id" binding:"required"`
|
|
Status string `json:"status" binding:"required"` // AttendanceStatus
|
|
Note *string `json:"note"`
|
|
}
|
|
|
|
// ReportAbsenceRequest for parents reporting absence
|
|
type ReportAbsenceRequest struct {
|
|
StudentID string `json:"student_id" binding:"required"`
|
|
StartDate string `json:"start_date" binding:"required"`
|
|
EndDate string `json:"end_date" binding:"required"`
|
|
Reason *string `json:"reason"`
|
|
ReasonCategory string `json:"reason_category" binding:"required"`
|
|
}
|
|
|
|
// CreateGradeRequest for creating a grade
|
|
type CreateGradeRequest struct {
|
|
StudentID string `json:"student_id" binding:"required"`
|
|
SubjectID string `json:"subject_id" binding:"required"`
|
|
SchoolYearID string `json:"school_year_id" binding:"required"`
|
|
Type string `json:"type" binding:"required"`
|
|
Value float64 `json:"value" binding:"required"`
|
|
Weight float64 `json:"weight"`
|
|
Date string `json:"date" binding:"required"`
|
|
Title *string `json:"title"`
|
|
Description *string `json:"description"`
|
|
Semester int `json:"semester" binding:"required"`
|
|
}
|
|
|
|
// StudentGradeOverview provides a summary of all grades for a student in a subject
|
|
type StudentGradeOverview struct {
|
|
Student Student `json:"student"`
|
|
Subject Subject `json:"subject"`
|
|
Grades []Grade `json:"grades"`
|
|
Average float64 `json:"average"`
|
|
OralAverage float64 `json:"oral_average"`
|
|
ExamAverage float64 `json:"exam_average"`
|
|
Semester int `json:"semester"`
|
|
}
|
|
|
|
// ClassAttendanceOverview provides attendance summary for a class
|
|
type ClassAttendanceOverview struct {
|
|
Class Class `json:"class"`
|
|
Date time.Time `json:"date"`
|
|
TotalStudents int `json:"total_students"`
|
|
PresentCount int `json:"present_count"`
|
|
AbsentCount int `json:"absent_count"`
|
|
LateCount int `json:"late_count"`
|
|
Records []AttendanceRecord `json:"records"`
|
|
}
|
|
|
|
// ParentDashboard provides a parent's view of their children's data
|
|
type ParentDashboard struct {
|
|
Children []StudentOverview `json:"children"`
|
|
UnreadMessages int `json:"unread_messages"`
|
|
UpcomingMeetings []ParentMeeting `json:"upcoming_meetings"`
|
|
RecentGrades []Grade `json:"recent_grades"`
|
|
PendingActions []string `json:"pending_actions"`
|
|
}
|
|
|
|
// StudentOverview provides summary info about a student
|
|
type StudentOverview struct {
|
|
Student Student `json:"student"`
|
|
Class Class `json:"class"`
|
|
ClassTeacher *Teacher `json:"class_teacher,omitempty"`
|
|
AttendanceRate float64 `json:"attendance_rate"`
|
|
UnexcusedAbsences int `json:"unexcused_absences"`
|
|
GradeAverage float64 `json:"grade_average"`
|
|
}
|
|
|
|
// TimetableView provides a formatted timetable for display
|
|
type TimetableView struct {
|
|
Class Class `json:"class"`
|
|
Week string `json:"week"` // ISO week: "2025-W01"
|
|
Days []TimetableDayView `json:"days"`
|
|
}
|
|
|
|
// TimetableDayView represents a single day in the timetable
|
|
type TimetableDayView struct {
|
|
Date time.Time `json:"date"`
|
|
DayName string `json:"day_name"`
|
|
Lessons []TimetableLessonView `json:"lessons"`
|
|
}
|
|
|
|
// TimetableLessonView represents a single lesson in the timetable view
|
|
type TimetableLessonView struct {
|
|
Slot TimetableSlot `json:"slot"`
|
|
Subject *Subject `json:"subject,omitempty"`
|
|
Teacher *Teacher `json:"teacher,omitempty"`
|
|
Room *string `json:"room,omitempty"`
|
|
IsSubstitution bool `json:"is_substitution"`
|
|
IsCancelled bool `json:"is_cancelled"`
|
|
Note *string `json:"note,omitempty"`
|
|
}
|