package services import ( "context" "github.com/breakpilot/school-service/internal/models" ) // Teacher-scoped constraint CRUD. Ownership is enforced via the parent // tt_teacher row's created_by_user_id (and tt_subject / tt_room for the // composite excluded-* constraints). // ---------- Teacher Unavailable Day ---------- func (s *TimetableService) CreateTeacherUnavailableDay(ctx context.Context, userID string, req *models.CreateTeacherUnavailableDayRequest) (*models.TeacherUnavailableDay, error) { var c models.TeacherUnavailableDay err := s.db.QueryRow(ctx, ` INSERT INTO tt_constraint_teacher_unavailable_day (created_by_user_id, teacher_id, day_of_week, is_hard, weight, active, note) SELECT $1, $2, $3, $4, $5, $6, $7 WHERE EXISTS (SELECT 1 FROM tt_teacher WHERE id = $2 AND created_by_user_id = $1) RETURNING id, created_by_user_id, teacher_id, day_of_week, is_hard, weight, active, COALESCE(note,''), created_at `, userID, req.TeacherID, req.DayOfWeek, req.IsHard, req.Weight, req.Active, req.Note).Scan( &c.ID, &c.CreatedByUserID, &c.TeacherID, &c.DayOfWeek, &c.IsHard, &c.Weight, &c.Active, &c.Note, &c.CreatedAt, ) return &c, err } func (s *TimetableService) ListTeacherUnavailableDays(ctx context.Context, userID string) ([]models.TeacherUnavailableDay, error) { rows, err := s.db.Query(ctx, ` SELECT id, created_by_user_id, teacher_id, day_of_week, is_hard, weight, active, COALESCE(note,''), created_at FROM tt_constraint_teacher_unavailable_day WHERE created_by_user_id = $1 ORDER BY teacher_id, day_of_week `, userID) if err != nil { return nil, err } defer rows.Close() var out []models.TeacherUnavailableDay for rows.Next() { var c models.TeacherUnavailableDay if err := rows.Scan(&c.ID, &c.CreatedByUserID, &c.TeacherID, &c.DayOfWeek, &c.IsHard, &c.Weight, &c.Active, &c.Note, &c.CreatedAt); err != nil { return nil, err } out = append(out, c) } return out, nil } func (s *TimetableService) DeleteTeacherUnavailableDay(ctx context.Context, id, userID string) error { _, err := s.db.Exec(ctx, `DELETE FROM tt_constraint_teacher_unavailable_day WHERE id = $1 AND created_by_user_id = $2`, id, userID) return err } // ---------- Teacher Unavailable Window ---------- func (s *TimetableService) CreateTeacherUnavailableWindow(ctx context.Context, userID string, req *models.CreateTeacherUnavailableWindowRequest) (*models.TeacherUnavailableWindow, error) { var c models.TeacherUnavailableWindow err := s.db.QueryRow(ctx, ` INSERT INTO tt_constraint_teacher_unavailable_window (created_by_user_id, teacher_id, day_of_week, start_time, end_time, is_hard, weight, active, note) SELECT $1, $2, $3, $4, $5, $6, $7, $8, $9 WHERE EXISTS (SELECT 1 FROM tt_teacher WHERE id = $2 AND created_by_user_id = $1) RETURNING id, created_by_user_id, teacher_id, day_of_week, start_time::text, end_time::text, is_hard, weight, active, COALESCE(note,''), created_at `, userID, req.TeacherID, req.DayOfWeek, req.StartTime, req.EndTime, req.IsHard, req.Weight, req.Active, req.Note).Scan( &c.ID, &c.CreatedByUserID, &c.TeacherID, &c.DayOfWeek, &c.StartTime, &c.EndTime, &c.IsHard, &c.Weight, &c.Active, &c.Note, &c.CreatedAt, ) return &c, err } func (s *TimetableService) ListTeacherUnavailableWindows(ctx context.Context, userID string) ([]models.TeacherUnavailableWindow, error) { rows, err := s.db.Query(ctx, ` SELECT id, created_by_user_id, teacher_id, day_of_week, start_time::text, end_time::text, is_hard, weight, active, COALESCE(note,''), created_at FROM tt_constraint_teacher_unavailable_window WHERE created_by_user_id = $1 ORDER BY teacher_id, day_of_week, start_time `, userID) if err != nil { return nil, err } defer rows.Close() var out []models.TeacherUnavailableWindow for rows.Next() { var c models.TeacherUnavailableWindow if err := rows.Scan(&c.ID, &c.CreatedByUserID, &c.TeacherID, &c.DayOfWeek, &c.StartTime, &c.EndTime, &c.IsHard, &c.Weight, &c.Active, &c.Note, &c.CreatedAt); err != nil { return nil, err } out = append(out, c) } return out, nil } func (s *TimetableService) DeleteTeacherUnavailableWindow(ctx context.Context, id, userID string) error { _, err := s.db.Exec(ctx, `DELETE FROM tt_constraint_teacher_unavailable_window WHERE id = $1 AND created_by_user_id = $2`, id, userID) return err } // ---------- Teacher Max Hours / Day ---------- func (s *TimetableService) CreateTeacherMaxHoursDay(ctx context.Context, userID string, req *models.CreateTeacherMaxHoursDayRequest) (*models.TeacherMaxHoursDay, error) { var c models.TeacherMaxHoursDay err := s.db.QueryRow(ctx, ` INSERT INTO tt_constraint_teacher_max_hours_day (created_by_user_id, teacher_id, max_hours, is_hard, weight, active, note) SELECT $1, $2, $3, $4, $5, $6, $7 WHERE EXISTS (SELECT 1 FROM tt_teacher WHERE id = $2 AND created_by_user_id = $1) RETURNING id, created_by_user_id, teacher_id, max_hours, is_hard, weight, active, COALESCE(note,''), created_at `, userID, req.TeacherID, req.MaxHours, req.IsHard, req.Weight, req.Active, req.Note).Scan( &c.ID, &c.CreatedByUserID, &c.TeacherID, &c.MaxHours, &c.IsHard, &c.Weight, &c.Active, &c.Note, &c.CreatedAt, ) return &c, err } func (s *TimetableService) ListTeacherMaxHoursDay(ctx context.Context, userID string) ([]models.TeacherMaxHoursDay, error) { rows, err := s.db.Query(ctx, ` SELECT id, created_by_user_id, teacher_id, max_hours, is_hard, weight, active, COALESCE(note,''), created_at FROM tt_constraint_teacher_max_hours_day WHERE created_by_user_id = $1 ORDER BY teacher_id `, userID) if err != nil { return nil, err } defer rows.Close() var out []models.TeacherMaxHoursDay for rows.Next() { var c models.TeacherMaxHoursDay if err := rows.Scan(&c.ID, &c.CreatedByUserID, &c.TeacherID, &c.MaxHours, &c.IsHard, &c.Weight, &c.Active, &c.Note, &c.CreatedAt); err != nil { return nil, err } out = append(out, c) } return out, nil } func (s *TimetableService) DeleteTeacherMaxHoursDay(ctx context.Context, id, userID string) error { _, err := s.db.Exec(ctx, `DELETE FROM tt_constraint_teacher_max_hours_day WHERE id = $1 AND created_by_user_id = $2`, id, userID) return err } // ---------- Teacher Max Hours / Week ---------- func (s *TimetableService) CreateTeacherMaxHoursWeek(ctx context.Context, userID string, req *models.CreateTeacherMaxHoursWeekRequest) (*models.TeacherMaxHoursWeek, error) { var c models.TeacherMaxHoursWeek err := s.db.QueryRow(ctx, ` INSERT INTO tt_constraint_teacher_max_hours_week (created_by_user_id, teacher_id, max_hours, is_hard, weight, active, note) SELECT $1, $2, $3, $4, $5, $6, $7 WHERE EXISTS (SELECT 1 FROM tt_teacher WHERE id = $2 AND created_by_user_id = $1) RETURNING id, created_by_user_id, teacher_id, max_hours, is_hard, weight, active, COALESCE(note,''), created_at `, userID, req.TeacherID, req.MaxHours, req.IsHard, req.Weight, req.Active, req.Note).Scan( &c.ID, &c.CreatedByUserID, &c.TeacherID, &c.MaxHours, &c.IsHard, &c.Weight, &c.Active, &c.Note, &c.CreatedAt, ) return &c, err } func (s *TimetableService) ListTeacherMaxHoursWeek(ctx context.Context, userID string) ([]models.TeacherMaxHoursWeek, error) { rows, err := s.db.Query(ctx, ` SELECT id, created_by_user_id, teacher_id, max_hours, is_hard, weight, active, COALESCE(note,''), created_at FROM tt_constraint_teacher_max_hours_week WHERE created_by_user_id = $1 ORDER BY teacher_id `, userID) if err != nil { return nil, err } defer rows.Close() var out []models.TeacherMaxHoursWeek for rows.Next() { var c models.TeacherMaxHoursWeek if err := rows.Scan(&c.ID, &c.CreatedByUserID, &c.TeacherID, &c.MaxHours, &c.IsHard, &c.Weight, &c.Active, &c.Note, &c.CreatedAt); err != nil { return nil, err } out = append(out, c) } return out, nil } func (s *TimetableService) DeleteTeacherMaxHoursWeek(ctx context.Context, id, userID string) error { _, err := s.db.Exec(ctx, `DELETE FROM tt_constraint_teacher_max_hours_week WHERE id = $1 AND created_by_user_id = $2`, id, userID) return err } // ---------- Teacher Excluded Subject ---------- func (s *TimetableService) CreateTeacherExcludedSubject(ctx context.Context, userID string, req *models.CreateTeacherExcludedSubjectRequest) (*models.TeacherExcludedSubject, error) { var c models.TeacherExcludedSubject err := s.db.QueryRow(ctx, ` INSERT INTO tt_constraint_teacher_excluded_subject (created_by_user_id, teacher_id, subject_id, is_hard, weight, active, note) SELECT $1, $2, $3, $4, $5, $6, $7 WHERE EXISTS (SELECT 1 FROM tt_teacher WHERE id = $2 AND created_by_user_id = $1) AND EXISTS (SELECT 1 FROM tt_subject WHERE id = $3 AND created_by_user_id = $1) RETURNING id, created_by_user_id, teacher_id, subject_id, is_hard, weight, active, COALESCE(note,''), created_at `, userID, req.TeacherID, req.SubjectID, req.IsHard, req.Weight, req.Active, req.Note).Scan( &c.ID, &c.CreatedByUserID, &c.TeacherID, &c.SubjectID, &c.IsHard, &c.Weight, &c.Active, &c.Note, &c.CreatedAt, ) return &c, err } func (s *TimetableService) ListTeacherExcludedSubjects(ctx context.Context, userID string) ([]models.TeacherExcludedSubject, error) { rows, err := s.db.Query(ctx, ` SELECT id, created_by_user_id, teacher_id, subject_id, is_hard, weight, active, COALESCE(note,''), created_at FROM tt_constraint_teacher_excluded_subject WHERE created_by_user_id = $1 ORDER BY teacher_id, subject_id `, userID) if err != nil { return nil, err } defer rows.Close() var out []models.TeacherExcludedSubject for rows.Next() { var c models.TeacherExcludedSubject if err := rows.Scan(&c.ID, &c.CreatedByUserID, &c.TeacherID, &c.SubjectID, &c.IsHard, &c.Weight, &c.Active, &c.Note, &c.CreatedAt); err != nil { return nil, err } out = append(out, c) } return out, nil } func (s *TimetableService) DeleteTeacherExcludedSubject(ctx context.Context, id, userID string) error { _, err := s.db.Exec(ctx, `DELETE FROM tt_constraint_teacher_excluded_subject WHERE id = $1 AND created_by_user_id = $2`, id, userID) return err } // ---------- Teacher Excluded Room ---------- func (s *TimetableService) CreateTeacherExcludedRoom(ctx context.Context, userID string, req *models.CreateTeacherExcludedRoomRequest) (*models.TeacherExcludedRoom, error) { var c models.TeacherExcludedRoom err := s.db.QueryRow(ctx, ` INSERT INTO tt_constraint_teacher_excluded_room (created_by_user_id, teacher_id, room_id, is_hard, weight, active, note) SELECT $1, $2, $3, $4, $5, $6, $7 WHERE EXISTS (SELECT 1 FROM tt_teacher WHERE id = $2 AND created_by_user_id = $1) AND EXISTS (SELECT 1 FROM tt_room WHERE id = $3 AND created_by_user_id = $1) RETURNING id, created_by_user_id, teacher_id, room_id, is_hard, weight, active, COALESCE(note,''), created_at `, userID, req.TeacherID, req.RoomID, req.IsHard, req.Weight, req.Active, req.Note).Scan( &c.ID, &c.CreatedByUserID, &c.TeacherID, &c.RoomID, &c.IsHard, &c.Weight, &c.Active, &c.Note, &c.CreatedAt, ) return &c, err } func (s *TimetableService) ListTeacherExcludedRooms(ctx context.Context, userID string) ([]models.TeacherExcludedRoom, error) { rows, err := s.db.Query(ctx, ` SELECT id, created_by_user_id, teacher_id, room_id, is_hard, weight, active, COALESCE(note,''), created_at FROM tt_constraint_teacher_excluded_room WHERE created_by_user_id = $1 ORDER BY teacher_id, room_id `, userID) if err != nil { return nil, err } defer rows.Close() var out []models.TeacherExcludedRoom for rows.Next() { var c models.TeacherExcludedRoom if err := rows.Scan(&c.ID, &c.CreatedByUserID, &c.TeacherID, &c.RoomID, &c.IsHard, &c.Weight, &c.Active, &c.Note, &c.CreatedAt); err != nil { return nil, err } out = append(out, c) } return out, nil } func (s *TimetableService) DeleteTeacherExcludedRoom(ctx context.Context, id, userID string) error { _, err := s.db.Exec(ctx, `DELETE FROM tt_constraint_teacher_excluded_room WHERE id = $1 AND created_by_user_id = $2`, id, userID) return err }