""" Teacher Context, Schoolyear Event & Recurring Routine Repositories. CRUD-Operationen fuer Schuljahres-Kontext (Phase 8). """ from datetime import datetime from typing import Optional, List, Dict, Any from sqlalchemy.orm import Session as DBSession from .context_models import ( TeacherContextDB, SchoolyearEventDB, RecurringRoutineDB, MacroPhaseEnum, EventTypeEnum, EventStatusEnum, RoutineTypeEnum, RecurrencePatternEnum, FEDERAL_STATES, SCHOOL_TYPES, ) class TeacherContextRepository: """Repository fuer Lehrer-Kontext CRUD-Operationen (Phase 8).""" def __init__(self, db: DBSession): self.db = db # ==================== CREATE / GET-OR-CREATE ==================== def get_or_create(self, teacher_id: str) -> TeacherContextDB: """ Holt den Kontext eines Lehrers oder erstellt einen neuen. Args: teacher_id: ID des Lehrers Returns: TeacherContextDB Model """ context = self.get_by_teacher_id(teacher_id) if context: return context # Neuen Kontext erstellen from uuid import uuid4 context = TeacherContextDB( id=str(uuid4()), teacher_id=teacher_id, macro_phase=MacroPhaseEnum.ONBOARDING, ) self.db.add(context) self.db.commit() self.db.refresh(context) return context # ==================== READ ==================== def get_by_teacher_id(self, teacher_id: str) -> Optional[TeacherContextDB]: """Holt den Kontext eines Lehrers.""" return self.db.query(TeacherContextDB).filter( TeacherContextDB.teacher_id == teacher_id ).first() # ==================== UPDATE ==================== def update_context( self, teacher_id: str, federal_state: str = None, school_type: str = None, schoolyear: str = None, schoolyear_start: datetime = None, macro_phase: str = None, current_week: int = None, ) -> Optional[TeacherContextDB]: """Aktualisiert den Kontext eines Lehrers.""" context = self.get_or_create(teacher_id) if federal_state is not None: context.federal_state = federal_state if school_type is not None: context.school_type = school_type if schoolyear is not None: context.schoolyear = schoolyear if schoolyear_start is not None: context.schoolyear_start = schoolyear_start if macro_phase is not None: context.macro_phase = MacroPhaseEnum(macro_phase) if current_week is not None: context.current_week = current_week self.db.commit() self.db.refresh(context) return context def complete_onboarding(self, teacher_id: str) -> TeacherContextDB: """Markiert Onboarding als abgeschlossen.""" context = self.get_or_create(teacher_id) context.onboarding_completed = True context.macro_phase = MacroPhaseEnum.SCHULJAHRESSTART self.db.commit() self.db.refresh(context) return context def update_flags( self, teacher_id: str, has_classes: bool = None, has_schedule: bool = None, is_exam_period: bool = None, is_before_holidays: bool = None, ) -> TeacherContextDB: """Aktualisiert die Status-Flags eines Kontexts.""" context = self.get_or_create(teacher_id) if has_classes is not None: context.has_classes = has_classes if has_schedule is not None: context.has_schedule = has_schedule if is_exam_period is not None: context.is_exam_period = is_exam_period if is_before_holidays is not None: context.is_before_holidays = is_before_holidays self.db.commit() self.db.refresh(context) return context def to_dict(self, context: TeacherContextDB) -> Dict[str, Any]: """Konvertiert DB-Model zu Dictionary.""" return { "id": context.id, "teacher_id": context.teacher_id, "school": { "federal_state": context.federal_state, "federal_state_name": FEDERAL_STATES.get(context.federal_state, ""), "school_type": context.school_type, "school_type_name": SCHOOL_TYPES.get(context.school_type, ""), }, "school_year": { "id": context.schoolyear, "start": context.schoolyear_start.isoformat() if context.schoolyear_start else None, "current_week": context.current_week, }, "macro_phase": { "id": context.macro_phase.value, "label": self._get_phase_label(context.macro_phase), }, "flags": { "onboarding_completed": context.onboarding_completed, "has_classes": context.has_classes, "has_schedule": context.has_schedule, "is_exam_period": context.is_exam_period, "is_before_holidays": context.is_before_holidays, }, "created_at": context.created_at.isoformat() if context.created_at else None, "updated_at": context.updated_at.isoformat() if context.updated_at else None, } def _get_phase_label(self, phase: MacroPhaseEnum) -> str: """Gibt den Anzeigenamen einer Makro-Phase zurueck.""" labels = { MacroPhaseEnum.ONBOARDING: "Einrichtung", MacroPhaseEnum.SCHULJAHRESSTART: "Schuljahresstart", MacroPhaseEnum.UNTERRICHTSAUFBAU: "Unterrichtsaufbau", MacroPhaseEnum.LEISTUNGSPHASE_1: "Leistungsphase 1", MacroPhaseEnum.HALBJAHRESABSCHLUSS: "Halbjahresabschluss", MacroPhaseEnum.LEISTUNGSPHASE_2: "Leistungsphase 2", MacroPhaseEnum.JAHRESABSCHLUSS: "Jahresabschluss", } return labels.get(phase, phase.value) class SchoolyearEventRepository: """Repository fuer Schuljahr-Events (Phase 8).""" def __init__(self, db: DBSession): self.db = db def create( self, teacher_id: str, title: str, start_date: datetime, event_type: str = "other", end_date: datetime = None, class_id: str = None, subject: str = None, description: str = "", needs_preparation: bool = True, reminder_days_before: int = 7, extra_data: Dict[str, Any] = None, ) -> SchoolyearEventDB: """Erstellt ein neues Schuljahr-Event.""" from uuid import uuid4 event = SchoolyearEventDB( id=str(uuid4()), teacher_id=teacher_id, title=title, event_type=EventTypeEnum(event_type), start_date=start_date, end_date=end_date, class_id=class_id, subject=subject, description=description, needs_preparation=needs_preparation, reminder_days_before=reminder_days_before, extra_data=extra_data or {}, ) self.db.add(event) self.db.commit() self.db.refresh(event) return event def get_by_id(self, event_id: str) -> Optional[SchoolyearEventDB]: """Holt ein Event nach ID.""" return self.db.query(SchoolyearEventDB).filter( SchoolyearEventDB.id == event_id ).first() def get_by_teacher( self, teacher_id: str, status: str = None, event_type: str = None, limit: int = 50, ) -> List[SchoolyearEventDB]: """Holt Events eines Lehrers.""" query = self.db.query(SchoolyearEventDB).filter( SchoolyearEventDB.teacher_id == teacher_id ) if status: query = query.filter(SchoolyearEventDB.status == EventStatusEnum(status)) if event_type: query = query.filter(SchoolyearEventDB.event_type == EventTypeEnum(event_type)) return query.order_by(SchoolyearEventDB.start_date).limit(limit).all() def get_upcoming( self, teacher_id: str, days: int = 30, limit: int = 10, ) -> List[SchoolyearEventDB]: """Holt anstehende Events der naechsten X Tage.""" from datetime import timedelta now = datetime.utcnow() end = now + timedelta(days=days) return self.db.query(SchoolyearEventDB).filter( SchoolyearEventDB.teacher_id == teacher_id, SchoolyearEventDB.start_date >= now, SchoolyearEventDB.start_date <= end, SchoolyearEventDB.status != EventStatusEnum.CANCELLED, ).order_by(SchoolyearEventDB.start_date).limit(limit).all() def update_status( self, event_id: str, status: str, preparation_done: bool = None, ) -> Optional[SchoolyearEventDB]: """Aktualisiert den Status eines Events.""" event = self.get_by_id(event_id) if not event: return None event.status = EventStatusEnum(status) if preparation_done is not None: event.preparation_done = preparation_done self.db.commit() self.db.refresh(event) return event def delete(self, event_id: str) -> bool: """Loescht ein Event.""" event = self.get_by_id(event_id) if not event: return False self.db.delete(event) self.db.commit() return True def to_dict(self, event: SchoolyearEventDB) -> Dict[str, Any]: """Konvertiert DB-Model zu Dictionary.""" return { "id": event.id, "teacher_id": event.teacher_id, "event_type": event.event_type.value, "title": event.title, "description": event.description, "start_date": event.start_date.isoformat() if event.start_date else None, "end_date": event.end_date.isoformat() if event.end_date else None, "class_id": event.class_id, "subject": event.subject, "status": event.status.value, "needs_preparation": event.needs_preparation, "preparation_done": event.preparation_done, "reminder_days_before": event.reminder_days_before, "extra_data": event.extra_data, "created_at": event.created_at.isoformat() if event.created_at else None, } class RecurringRoutineRepository: """Repository fuer wiederkehrende Routinen (Phase 8).""" def __init__(self, db: DBSession): self.db = db def create( self, teacher_id: str, title: str, routine_type: str = "other", recurrence_pattern: str = "weekly", day_of_week: int = None, day_of_month: int = None, time_of_day: str = None, # Format: "14:00" duration_minutes: int = 60, description: str = "", valid_from: datetime = None, valid_until: datetime = None, ) -> RecurringRoutineDB: """Erstellt eine neue wiederkehrende Routine.""" from uuid import uuid4 from datetime import time as dt_time time_obj = None if time_of_day: parts = time_of_day.split(":") time_obj = dt_time(int(parts[0]), int(parts[1])) routine = RecurringRoutineDB( id=str(uuid4()), teacher_id=teacher_id, title=title, routine_type=RoutineTypeEnum(routine_type), recurrence_pattern=RecurrencePatternEnum(recurrence_pattern), day_of_week=day_of_week, day_of_month=day_of_month, time_of_day=time_obj, duration_minutes=duration_minutes, description=description, valid_from=valid_from, valid_until=valid_until, ) self.db.add(routine) self.db.commit() self.db.refresh(routine) return routine def get_by_id(self, routine_id: str) -> Optional[RecurringRoutineDB]: """Holt eine Routine nach ID.""" return self.db.query(RecurringRoutineDB).filter( RecurringRoutineDB.id == routine_id ).first() def get_by_teacher( self, teacher_id: str, is_active: bool = True, routine_type: str = None, ) -> List[RecurringRoutineDB]: """Holt Routinen eines Lehrers.""" query = self.db.query(RecurringRoutineDB).filter( RecurringRoutineDB.teacher_id == teacher_id ) if is_active is not None: query = query.filter(RecurringRoutineDB.is_active == is_active) if routine_type: query = query.filter(RecurringRoutineDB.routine_type == RoutineTypeEnum(routine_type)) return query.all() def get_today(self, teacher_id: str) -> List[RecurringRoutineDB]: """Holt Routinen die heute stattfinden.""" today = datetime.utcnow() day_of_week = today.weekday() # 0 = Montag day_of_month = today.day routines = self.get_by_teacher(teacher_id, is_active=True) today_routines = [] for routine in routines: if routine.recurrence_pattern == RecurrencePatternEnum.DAILY: today_routines.append(routine) elif routine.recurrence_pattern == RecurrencePatternEnum.WEEKLY: if routine.day_of_week == day_of_week: today_routines.append(routine) elif routine.recurrence_pattern == RecurrencePatternEnum.BIWEEKLY: # Vereinfacht: Pruefen ob Tag passt (echte Logik braucht Startdatum) if routine.day_of_week == day_of_week: today_routines.append(routine) elif routine.recurrence_pattern == RecurrencePatternEnum.MONTHLY: if routine.day_of_month == day_of_month: today_routines.append(routine) return today_routines def update( self, routine_id: str, title: str = None, is_active: bool = None, day_of_week: int = None, time_of_day: str = None, ) -> Optional[RecurringRoutineDB]: """Aktualisiert eine Routine.""" routine = self.get_by_id(routine_id) if not routine: return None if title is not None: routine.title = title if is_active is not None: routine.is_active = is_active if day_of_week is not None: routine.day_of_week = day_of_week if time_of_day is not None: from datetime import time as dt_time parts = time_of_day.split(":") routine.time_of_day = dt_time(int(parts[0]), int(parts[1])) self.db.commit() self.db.refresh(routine) return routine def delete(self, routine_id: str) -> bool: """Loescht eine Routine.""" routine = self.get_by_id(routine_id) if not routine: return False self.db.delete(routine) self.db.commit() return True def to_dict(self, routine: RecurringRoutineDB) -> Dict[str, Any]: """Konvertiert DB-Model zu Dictionary.""" return { "id": routine.id, "teacher_id": routine.teacher_id, "routine_type": routine.routine_type.value, "title": routine.title, "description": routine.description, "recurrence_pattern": routine.recurrence_pattern.value, "day_of_week": routine.day_of_week, "day_of_month": routine.day_of_month, "time_of_day": routine.time_of_day.isoformat() if routine.time_of_day else None, "duration_minutes": routine.duration_minutes, "is_active": routine.is_active, "valid_from": routine.valid_from.isoformat() if routine.valid_from else None, "valid_until": routine.valid_until.isoformat() if routine.valid_until else None, "created_at": routine.created_at.isoformat() if routine.created_at else None, }