[split-required] [guardrail-change] Enforce 500 LOC budget across all services
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>
This commit is contained in:
382
consent-service/internal/models/school_operations.go
Normal file
382
consent-service/internal/models/school_operations.go
Normal file
@@ -0,0 +1,382 @@
|
||||
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"`
|
||||
}
|
||||
Reference in New Issue
Block a user