package services import ( "context" "github.com/breakpilot/school-service/internal/models" ) // Subject-scoped constraint CRUD. Ownership via tt_subject.created_by_user_id. // ---------- Subject Min Day Gap ---------- func (s *TimetableService) CreateSubjectMinDayGap(ctx context.Context, userID string, req *models.CreateSubjectMinDayGapRequest) (*models.SubjectMinDayGap, error) { var c models.SubjectMinDayGap err := s.db.QueryRow(ctx, ` INSERT INTO tt_constraint_subject_min_day_gap (created_by_user_id, subject_id, min_gap_days, is_hard, weight, active, note) SELECT $1, $2, $3, $4, $5, $6, $7 WHERE EXISTS (SELECT 1 FROM tt_subject WHERE id = $2 AND created_by_user_id = $1) RETURNING id, created_by_user_id, subject_id, min_gap_days, is_hard, weight, active, COALESCE(note,''), created_at `, userID, req.SubjectID, req.MinGapDays, req.IsHard, req.Weight, req.Active, req.Note).Scan( &c.ID, &c.CreatedByUserID, &c.SubjectID, &c.MinGapDays, &c.IsHard, &c.Weight, &c.Active, &c.Note, &c.CreatedAt, ) return &c, err } func (s *TimetableService) ListSubjectMinDayGaps(ctx context.Context, userID string) ([]models.SubjectMinDayGap, error) { rows, err := s.db.Query(ctx, ` SELECT id, created_by_user_id, subject_id, min_gap_days, is_hard, weight, active, COALESCE(note,''), created_at FROM tt_constraint_subject_min_day_gap WHERE created_by_user_id = $1 ORDER BY subject_id `, userID) if err != nil { return nil, err } defer rows.Close() var out []models.SubjectMinDayGap for rows.Next() { var c models.SubjectMinDayGap if err := rows.Scan(&c.ID, &c.CreatedByUserID, &c.SubjectID, &c.MinGapDays, &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) DeleteSubjectMinDayGap(ctx context.Context, id, userID string) error { _, err := s.db.Exec(ctx, `DELETE FROM tt_constraint_subject_min_day_gap WHERE id = $1 AND created_by_user_id = $2`, id, userID) return err } // ---------- Subject Max Consecutive ---------- func (s *TimetableService) CreateSubjectMaxConsecutive(ctx context.Context, userID string, req *models.CreateSubjectMaxConsecutiveRequest) (*models.SubjectMaxConsecutive, error) { var c models.SubjectMaxConsecutive err := s.db.QueryRow(ctx, ` INSERT INTO tt_constraint_subject_max_consecutive (created_by_user_id, subject_id, max_consecutive, is_hard, weight, active, note) SELECT $1, $2, $3, $4, $5, $6, $7 WHERE EXISTS (SELECT 1 FROM tt_subject WHERE id = $2 AND created_by_user_id = $1) RETURNING id, created_by_user_id, subject_id, max_consecutive, is_hard, weight, active, COALESCE(note,''), created_at `, userID, req.SubjectID, req.MaxConsecutive, req.IsHard, req.Weight, req.Active, req.Note).Scan( &c.ID, &c.CreatedByUserID, &c.SubjectID, &c.MaxConsecutive, &c.IsHard, &c.Weight, &c.Active, &c.Note, &c.CreatedAt, ) return &c, err } func (s *TimetableService) ListSubjectMaxConsecutives(ctx context.Context, userID string) ([]models.SubjectMaxConsecutive, error) { rows, err := s.db.Query(ctx, ` SELECT id, created_by_user_id, subject_id, max_consecutive, is_hard, weight, active, COALESCE(note,''), created_at FROM tt_constraint_subject_max_consecutive WHERE created_by_user_id = $1 ORDER BY subject_id `, userID) if err != nil { return nil, err } defer rows.Close() var out []models.SubjectMaxConsecutive for rows.Next() { var c models.SubjectMaxConsecutive if err := rows.Scan(&c.ID, &c.CreatedByUserID, &c.SubjectID, &c.MaxConsecutive, &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) DeleteSubjectMaxConsecutive(ctx context.Context, id, userID string) error { _, err := s.db.Exec(ctx, `DELETE FROM tt_constraint_subject_max_consecutive WHERE id = $1 AND created_by_user_id = $2`, id, userID) return err } // ---------- Subject Contiguous When Repeated ---------- func (s *TimetableService) CreateSubjectContiguousWhenRepeated(ctx context.Context, userID string, req *models.CreateSubjectContiguousWhenRepeatedRequest) (*models.SubjectContiguousWhenRepeated, error) { var c models.SubjectContiguousWhenRepeated err := s.db.QueryRow(ctx, ` INSERT INTO tt_constraint_subject_contiguous_when_repeated (created_by_user_id, subject_id, is_hard, weight, active, note) SELECT $1, $2, $3, $4, $5, $6 WHERE EXISTS (SELECT 1 FROM tt_subject WHERE id = $2 AND created_by_user_id = $1) RETURNING id, created_by_user_id, subject_id, is_hard, weight, active, COALESCE(note,''), created_at `, userID, req.SubjectID, req.IsHard, req.Weight, req.Active, req.Note).Scan( &c.ID, &c.CreatedByUserID, &c.SubjectID, &c.IsHard, &c.Weight, &c.Active, &c.Note, &c.CreatedAt, ) return &c, err } func (s *TimetableService) ListSubjectContiguousWhenRepeated(ctx context.Context, userID string) ([]models.SubjectContiguousWhenRepeated, error) { rows, err := s.db.Query(ctx, ` SELECT id, created_by_user_id, subject_id, is_hard, weight, active, COALESCE(note,''), created_at FROM tt_constraint_subject_contiguous_when_repeated WHERE created_by_user_id = $1 ORDER BY subject_id `, userID) if err != nil { return nil, err } defer rows.Close() var out []models.SubjectContiguousWhenRepeated for rows.Next() { var c models.SubjectContiguousWhenRepeated if err := rows.Scan(&c.ID, &c.CreatedByUserID, &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) DeleteSubjectContiguousWhenRepeated(ctx context.Context, id, userID string) error { _, err := s.db.Exec(ctx, `DELETE FROM tt_constraint_subject_contiguous_when_repeated WHERE id = $1 AND created_by_user_id = $2`, id, userID) return err } // ---------- Subject Preferred Period ---------- func (s *TimetableService) CreateSubjectPreferredPeriod(ctx context.Context, userID string, req *models.CreateSubjectPreferredPeriodRequest) (*models.SubjectPreferredPeriod, error) { var c models.SubjectPreferredPeriod err := s.db.QueryRow(ctx, ` INSERT INTO tt_constraint_subject_preferred_period (created_by_user_id, subject_id, period_from, period_to, is_hard, weight, active, note) SELECT $1, $2, $3, $4, $5, $6, $7, $8 WHERE EXISTS (SELECT 1 FROM tt_subject WHERE id = $2 AND created_by_user_id = $1) RETURNING id, created_by_user_id, subject_id, period_from, period_to, is_hard, weight, active, COALESCE(note,''), created_at `, userID, req.SubjectID, req.PeriodFrom, req.PeriodTo, req.IsHard, req.Weight, req.Active, req.Note).Scan( &c.ID, &c.CreatedByUserID, &c.SubjectID, &c.PeriodFrom, &c.PeriodTo, &c.IsHard, &c.Weight, &c.Active, &c.Note, &c.CreatedAt, ) return &c, err } func (s *TimetableService) ListSubjectPreferredPeriods(ctx context.Context, userID string) ([]models.SubjectPreferredPeriod, error) { rows, err := s.db.Query(ctx, ` SELECT id, created_by_user_id, subject_id, period_from, period_to, is_hard, weight, active, COALESCE(note,''), created_at FROM tt_constraint_subject_preferred_period WHERE created_by_user_id = $1 ORDER BY subject_id `, userID) if err != nil { return nil, err } defer rows.Close() var out []models.SubjectPreferredPeriod for rows.Next() { var c models.SubjectPreferredPeriod if err := rows.Scan(&c.ID, &c.CreatedByUserID, &c.SubjectID, &c.PeriodFrom, &c.PeriodTo, &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) DeleteSubjectPreferredPeriod(ctx context.Context, id, userID string) error { _, err := s.db.Exec(ctx, `DELETE FROM tt_constraint_subject_preferred_period WHERE id = $1 AND created_by_user_id = $2`, id, userID) return err } // ---------- Subject Double Lesson ---------- func (s *TimetableService) CreateSubjectDoubleLesson(ctx context.Context, userID string, req *models.CreateSubjectDoubleLessonRequest) (*models.SubjectDoubleLesson, error) { var c models.SubjectDoubleLesson err := s.db.QueryRow(ctx, ` INSERT INTO tt_constraint_subject_double_lesson (created_by_user_id, subject_id, is_hard, weight, active, note) SELECT $1, $2, $3, $4, $5, $6 WHERE EXISTS (SELECT 1 FROM tt_subject WHERE id = $2 AND created_by_user_id = $1) RETURNING id, created_by_user_id, subject_id, is_hard, weight, active, COALESCE(note,''), created_at `, userID, req.SubjectID, req.IsHard, req.Weight, req.Active, req.Note).Scan( &c.ID, &c.CreatedByUserID, &c.SubjectID, &c.IsHard, &c.Weight, &c.Active, &c.Note, &c.CreatedAt, ) return &c, err } func (s *TimetableService) ListSubjectDoubleLessons(ctx context.Context, userID string) ([]models.SubjectDoubleLesson, error) { rows, err := s.db.Query(ctx, ` SELECT id, created_by_user_id, subject_id, is_hard, weight, active, COALESCE(note,''), created_at FROM tt_constraint_subject_double_lesson WHERE created_by_user_id = $1 ORDER BY subject_id `, userID) if err != nil { return nil, err } defer rows.Close() var out []models.SubjectDoubleLesson for rows.Next() { var c models.SubjectDoubleLesson if err := rows.Scan(&c.ID, &c.CreatedByUserID, &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) DeleteSubjectDoubleLesson(ctx context.Context, id, userID string) error { _, err := s.db.Exec(ctx, `DELETE FROM tt_constraint_subject_double_lesson WHERE id = $1 AND created_by_user_id = $2`, id, userID) return err }