Files
breakpilot-lehrer/school-service/internal/models/calendar.go
T
Benjamin Admin 33409352ee
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-school (push) Successful in 28s
CI / test-go-edu-search (push) Successful in 28s
CI / test-python-klausur (push) Failing after 2m38s
CI / test-python-agent-core (push) Successful in 20s
CI / test-nodejs-website (push) Successful in 26s
Phase 9b: Schul-Events CRUD + Schuljahres-Rollover
Backend (school-service):
  - calendar_events.go — Create/List/Delete on cal_school_event with
    UUID[] handling for affected_class_ids. Default lead-days [7,1]
    if caller omits the array.
  - calendar_rollover.go — single-transaction promotion: graduating
    classes (grade >= 13) get deleted first so the +1 update doesn't
    bump them to invalid grade 14. defaultSchoolYearDates() picks the
    next Aug-Jul pair when the caller doesn't specify.
  - Handlers + routes: GET/POST /calendar/events,
    DELETE /calendar/events/:id, POST /calendar/school-year-rollover.

Frontend (studio-v2):
  - EventModal: form with Title / Typ / Datum/Zeit / unterrichtsfrei /
    Beschreibung / Sichtbarkeit + Notification-Checkboxen. Per-Type
    Farb-Mapping in types.ts.
  - DayDetail: Modal das beim Klick auf einen Kalender-Tag aufgeht und
    Feiertage + Schulferien + Schul-Events fuer diesen Tag listet,
    inkl. Loeschen-Button pro Event.
  - RolloverWizard: zwei-Schritt-Dialog mit Datums-Auswahl + Tipp-
    Bestaetigung ("SCHULJAHR WECHSELN") gegen versehentliche Auslo-
    sung, danach Ergebnis-Card mit promoted/graduated-Counts.
  - MonthView gewinnt onDayClick + onAddEvent + onRollover Props,
    rendert farb-codierte Punkte fuer School-Events am Tagesrand.
  - Page laed Events parallel mit Holidays und reicht alle Handler
    nach unten.

Tests:
  - Go: 3 neue Tests fuer defaultSchoolYearDates + parseClassIDs.
    Validator-Test fuer CreateSchoolEventRequest existiert bereits.
    80 Subtests gesamt, alle gruen.
  - Playwright: mockCalendarApi gewinnt Routes fuer events GET/POST/
    DELETE und school-year-rollover. 6 neue Tests (EventModal open,
    submit, DayDetail open, Rollover-Trigger, Confirm-Schutz,
    Ergebnis-Anzeige).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 10:32:33 +02:00

98 lines
4.7 KiB
Go

package models
import (
"time"
"github.com/google/uuid"
)
// PublicEvent is a holiday or school-vacation row imported from
// OpenHolidaysAPI. Global (no owner) — same for every school per region.
type PublicEvent struct {
ID uuid.UUID `json:"id" db:"id"`
Region string `json:"region" db:"region"` // e.g. "DE-NI"
EventType string `json:"event_type" db:"event_type"` // public_holiday | school_holiday
NameDe string `json:"name_de" db:"name_de"`
NameEn string `json:"name_en,omitempty" db:"name_en"`
StartDate string `json:"start_date" db:"start_date"` // YYYY-MM-DD
EndDate string `json:"end_date" db:"end_date"`
Source string `json:"source,omitempty" db:"source"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// SchoolCalendarConfig stores the Bundesland selection for one school
// (= one Rektor account). One row per user.
type SchoolCalendarConfig struct {
UserID uuid.UUID `json:"user_id" db:"user_id"`
Bundesland string `json:"bundesland" db:"bundesland"` // DE-NI ...
SchoolYearStart *string `json:"school_year_start,omitempty" db:"school_year_start"`
SchoolYearEnd *string `json:"school_year_end,omitempty" db:"school_year_end"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
}
// SchoolEvent is a user-managed event (Fortbildung, Schulfeier, …).
type SchoolEvent struct {
ID uuid.UUID `json:"id" db:"id"`
CreatedByUserID uuid.UUID `json:"created_by_user_id" db:"created_by_user_id"`
Title string `json:"title" db:"title"`
Description string `json:"description,omitempty" db:"description"`
EventType string `json:"event_type" db:"event_type"`
IsSchoolFree bool `json:"is_school_free" db:"is_school_free"`
StartDate string `json:"start_date" db:"start_date"`
EndDate string `json:"end_date" db:"end_date"`
StartTime *string `json:"start_time,omitempty" db:"start_time"`
EndTime *string `json:"end_time,omitempty" db:"end_time"`
AffectedClassIDs []uuid.UUID `json:"affected_class_ids" db:"affected_class_ids"`
VisibleToParents bool `json:"visible_to_parents" db:"visible_to_parents"`
NotifyParents bool `json:"notify_parents" db:"notify_parents"`
NotifyStudents bool `json:"notify_students" db:"notify_students"`
NotificationLeadDays []int `json:"notification_lead_days" db:"notification_lead_days"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
}
// Request DTOs
// UpsertSchoolCalendarConfigRequest sets or updates the Bundesland for the
// authenticated user. Both school-year dates are optional (defaults to the
// running year based on today's date).
type UpsertSchoolCalendarConfigRequest struct {
Bundesland string `json:"bundesland" binding:"required,len=5"`
SchoolYearStart *string `json:"school_year_start,omitempty"`
SchoolYearEnd *string `json:"school_year_end,omitempty"`
}
type CreateSchoolEventRequest struct {
Title string `json:"title" binding:"required"`
Description string `json:"description"`
EventType string `json:"event_type" binding:"required,oneof=fortbildung schulfeier klassenfahrt projekttag eltern_info andere"`
IsSchoolFree bool `json:"is_school_free"`
StartDate string `json:"start_date" binding:"required"`
EndDate string `json:"end_date" binding:"required"`
StartTime *string `json:"start_time,omitempty"`
EndTime *string `json:"end_time,omitempty"`
AffectedClassIDs []string `json:"affected_class_ids"`
VisibleToParents bool `json:"visible_to_parents"`
NotifyParents bool `json:"notify_parents"`
NotifyStudents bool `json:"notify_students"`
NotificationLeadDays []int `json:"notification_lead_days"`
}
// SchoolYearRolloverRequest moves all classes up by one grade and updates
// the config's school-year dates. Optional date pair, otherwise defaults
// to next Aug 01 → following Jul 31.
type SchoolYearRolloverRequest struct {
NewYearStart *string `json:"new_year_start,omitempty"` // YYYY-MM-DD
NewYearEnd *string `json:"new_year_end,omitempty"`
}
// SchoolYearRolloverResult is what the endpoint returns so the UI can show
// "promoted 8 classes, removed 2 graduating ones".
type SchoolYearRolloverResult struct {
ClassesPromoted int `json:"classes_promoted"`
ClassesGraduated int `json:"classes_graduated"`
NewYearStart string `json:"new_year_start"`
NewYearEnd string `json:"new_year_end"`
}