24 KiB
📋 Бизнес-логика
Ролевая модель
Система поддерживает шесть ролей пользователей:
| Роль | Enum | Возможности |
|---|---|---|
| Администратор | ADMIN |
Полный доступ: пользователи, справочники, тенанты, роли, архивирование и восстановление. |
| Учебный отдел | EDUCATION_OFFICE |
Редактирование расписания, точечные переносы/замены/отмены, временные слоты, аудитории, загруженность. |
| Кафедра | DEPARTMENT |
Дисциплины своей кафедры, загрузка дисциплин, привязки преподавателей, заявки на создание преподавателей, комментарии, расписание и нагрузка кафедры. |
| Просмотр расписаний | SCHEDULE_VIEWER |
Read-only просмотр расписаний по группам, преподавателям, аудиториям и кафедрам в режиме одной активной совмещённой таблицы чётной/нечётной недели. |
| Преподаватель | TEACHER |
Просмотр своего расписания. В перспективе — подача заявок на перенос. |
| Студент | STUDENT |
Только просмотр расписания (Read-only). |
После авторизации пользователь перенаправляется на свой интерфейс:
ADMIN→/admin/EDUCATION_OFFICE→/admin/#schedule-viewDEPARTMENT→/admin/#department-workspaceSCHEDULE_VIEWER→/admin/#schedule-viewTEACHER→/teacher/STUDENT→/student/
Bearer-токен проверяется на backend. Frontend-скрытие пунктов меню является только удобством, а не источником прав.
Для роли DEPARTMENT backend дополнительно ограничивает изменения рамками кафедры текущего пользователя:
- загрузка и комментарии дисциплин идут через
/api/department/*; - общий
/api/subjectsдоступен кафедре только на чтение; - привязки
/api/teacher-subjectsразрешены только если преподаватель и дисциплина относятся к кафедре текущего пользователя; - добавление существующего преподавателя через
/api/department/teachers/{teacherId}/assignmentsсоздаёт связь только со своей кафедрой; - заявки
/api/department/teacher-requestsсоздаются и просматриваются кафедрой только в рамках своей кафедры, а создание пользователя выполняет администратор после проверки.
Управление ресурсами
Кафедры (Departments)
Организационные единицы университета. К кафедре привязываются пользователи, группы и дисциплины.
- Имеют уникальный числовой
code - Предзаполнены: «Кафедра ИБ», «Кафедра ВТ», «Кафедра КТ»
Специальности (Specialties)
Учебные направления с кодом по ФГОС. У одной специальности может быть несколько профилей обучения, например базовый профиль и профиль конкретной образовательной программы.
- Примеры: «Информационная безопасность» (10.03.01), «Программная инженерия» (09.03.04)
- При создании специальности автоматически создаётся профиль
Без профиля
Формы обучения (Education Forms)
Уровни/формы обучения для привязки к группам и календарным учебным графикам.
- Предзаполнены: Бакалавриат, Магистратура, Специалитет
- Нельзя удалить форму обучения, если к ней привязаны группы или календарные графики
Учебные группы (Student Groups)
- Поля: Название, численность, форма обучения, кафедра, специальность, профиль обучения, год начала обучения
- Курс: вычисляется относительно учебного года:
год начала учебного года - year_start_study + 1, но до начала обучения отдаётся как0, а не отрицательное число - Подгруппы: Возможно деление группы на подгруппы (таблица
subgroups) - Календарь: на каждый учебный год группе назначается конкретный календарный учебный график
- Дисциплины графика: при назначении графика группе отображаются дисциплины, вручную привязанные к номерам семестров этого графика
- Завершение обучения: если текущий курс больше
course_countназначенного календарного графика, группа считается завершившей обучение и не попадает в обычные списки выбора. Историческое расписание по датам периода обучения остаётся доступным.
Аудитории (Classrooms)
- Поля: Название (уникальное), вместимость (> 0), корпус, этаж, доступность
- Оборудование: К каждой аудитории привязывается список оборудования (Many-to-Many) с указанием количества
- Статус: Флаг
is_availableдля блокирования назначения пар - Жизненный цикл:
status=ARCHIVEDозначает вывод из эксплуатации. Такая аудитория отображается в прошлом расписании, но запрещена для новых назначений.
Оборудование (Equipments)
Каталог оборудования для привязки к аудиториям.
- Предзаполнены: Проектор, ПК, Лаборатория, Интерактивная доска, Документ-камера, Аудиосистема
- Уникальность по названию
Дисциплины (Subjects)
- Поля: Название (уникальное), код, кафедра, описание
- Привязка преподавателей через
teacher_subjects(Many-to-Many) - Кафедра может добавлять комментарии к дисциплине и загружать список дисциплин через кабинет кафедры
Жизненный цикл справочников
Справочники, которые участвуют в расписании и отчётах, не удаляются физически. Для них используется архивирование:
ACTIVE— запись доступна для выбора;ARCHIVED— запись остаётся в истории, но не используется в будущих назначениях.
Архивирование уже применяется к пользователям, аудиториям, оборудованию, кафедрам, специальностям, группам, подгруппам, дисциплинам и профилям обучения. Исторические отчёты используют записи, действовавшие на дату занятия.
Методическая проверка активности учитывает период действия и status: архивная запись без active_to не считается активной, но запись с заполненным active_to остаётся активной для исторических дат до даты вывода из работы.
Кафедральные связи преподавателей
Основная кафедра преподавателя хранится в users.department_id для совместимости, а актуальные и исторические связи фиксируются в teacher_department_assignments. Преподаватель может быть связан с несколькими кафедрами: одна связь остаётся основной, дополнительные связи создаются как неосновные.
Правила:
- у преподавателя должна быть одна открытая основная кафедра;
- преподаватель может иметь несколько открытых неосновных кафедр;
- одна открытая пара
teacher_id+department_idзапрещает дубли одной и той же связи; - при переводе старая запись закрывается датой
valid_to, новая открывается сvalid_from; - кафедра может добавить существующего активного преподавателя только на свою кафедру, без смены его основной кафедры;
- кафедра может отправить заявку на создание нового преподавателя, но заявка не хранит пароль;
- администратор при одобрении заявки может скорректировать кафедру, логин, ФИО и должность, задаёт пароль и создаёт пользователя с ролью
TEACHER; - расписание и отчёты за прошлые периоды не теряют связь с прежней кафедрой.
Логика расписания
Динамическая модель расписания
Основная модель расписания строится из правил, а не из отдельных статических пар.
| Компонент | Назначение |
|---|---|
academic_years / semesters |
Учебные годы и семестры. Неделя 1 считается от semesters.start_date |
specialty_profiles |
Профили обучения внутри специальности |
academic_calendars |
Календарный учебный график профиля, формы обучения и учебного года |
academic_calendar_activity_types |
Коды Excel-графика: Т, Э, К, У, П, Пд, Н, Г, Д, ПА, С, *, = |
academic_calendar_days |
Дневная сетка по курсу и дате |
academic_calendar_subjects |
Дисциплины графика по номерам учебных семестров |
student_group_calendar_assignments |
Назначение конкретного графика группе на учебный год |
time_slots |
Настраиваемая сетка пар для тенанта |
schedule_rules |
Лимиты часов и недели начала по лекциям, лабораторным и практикам |
schedule_rule_groups |
Группы правила, включая потоковые лекции |
schedule_rule_slots |
День, чётность, слот, преподаватель, аудитория, тип и формат занятия |
schedule_rule_slot_subgroups |
Подгруппы лабораторного слота |
schedule_overrides |
Точечные переносы, отмены и замены конкретных сгенерированных пар |
Генератор ScheduleGeneratorService рендерит расписание по запросу:
- Определяет семестр для каждой даты диапазона.
- Вычисляет номер недели и чётность.
- Находит назначенный группе календарный учебный график на учебный год даты.
- Определяет код активности по курсу группы и конкретной дате.
- Пропускает день, если код активности не разрешает обычные пары.
- Загружает правила группы или преподавателя.
- Для каждого типа занятия проверяет свою неделю начала в семестре.
- Подставляет эффективную сетку времени даты: ручную, субботнюю или базовую.
- Считает уже проведённые часы отдельно для лекций, практик и каждой лабораторной подгруппы.
- Останавливает вывод слотов конкретного типа, когда достигнут его лимит часов.
- Применяет точечные изменения из
schedule_overridesв расширенном поиске и отчётах.
Расход часов считается в пределах одного построения расписания: при первом использовании семестра генератор одним последовательным проходом прогревает проведённые часы от начала семестра до начала запрошенного диапазона, затем ведёт локальный прогресс по правилу, типу занятия, группе и подгруппе. Обратного пересчёта прошлых дат для каждого слота нет. Singleton-кэш в сервисе не используется, поэтому данные расписания не накапливаются в heap между запросами и не устаревают после изменений правил, календаря или подгрупп.
В генерацию попадают только активные на дату правила, дисциплины, группы, преподаватели и аудитории. Для будущих дат аудитория с is_available=false не выводится в расписании, но прошлые занятия остаются доступными для просмотра.
Расширенный поиск расписания ограничивает широкие запросы: если не указаны groupId и departmentId, сервис не будет обходить больше 50 активных групп и вернёт ошибку валидации. Запросы по одному teacherId без группы или кафедры строятся через генерацию расписания преподавателя, чтобы не выполнять полный перебор групп.
Лабораторные работы могут делиться на подгруппы через schedule_rule_slot_subgroups. Если подгруппы выбраны, занятие выводится только для родительских групп этих подгрупп, а лимит лабораторных часов списывается отдельно по каждой подгруппе. Если лабораторная проводится у нескольких групп одновременно, один слот может содержать разные подгруппы разных групп. Лекции и практики не делятся на подгруппы.
Обычные пары генерируются только на коде Т (allow_schedule = true). Экзамены, каникулы, практики, нерабочие дни, праздники * и дни вне учебного года = считаются пропуском: занятие не переносится и не списывает академические часы. Если у группы нет назначения графика на учебный год, GET /api/schedule возвращает пустой список для этой группы без ошибки.
Временные слоты
Сетки времени хранятся в time_slot_scopes, сами пары — в time_slots. Базовая сетка (DEFAULT) применяется по умолчанию, субботняя (WEEKDAY, day_of_week = 6) применяется автоматически по субботам, а пользовательские сетки (MANUAL) применяются только через time_slot_date_assignments на конкретные даты. Ручное назначение выполняется из ячейки редактора календарного графика, потому что оно относится к конкретной учебной дате.
Правило расписания выбирает базовую пару по номеру. При генерации ScheduleGeneratorService сначала проверяет ручное назначение даты, затем автоматическую субботнюю сетку, затем базовую сетку. Если в выбранной сетке нет пары с нужным номером, используется базовый слот. Ручная сетка меняет только время занятий и не включает пары в дни, где календарный учебный график запрещает обычное расписание.
При миграции одинаковые стартовые слоты создаются для базовой и субботней сетки:
| № | Время |
|---|---|
| 1 | 08:00 – 09:30 |
| 2 | 09:40 – 11:10 |
| 3 | 11:40 – 13:10 |
| 4 | 13:20 – 14:50 |
| 5 | 15:00 – 16:30 |
| 6 | 16:50 – 18:20 |
| 7 | 18:30 – 20:00 |
Валидация правил расписания
- Правило: обязательны дисциплина, семестр, хотя бы один положительный лимит часов по типу занятий, положительные недели начала и хотя бы одна группа.
- Покрытие типов: если для лекций, лабораторных или практик указан лимит часов, должен быть хотя бы один слот этого типа; слот типа не сохраняется с нулевым лимитом часов.
- Слот: день недели должен быть от 1 до 7, чётность недели обязательна.
- Связанные сущности: базовый временной слот, преподаватель, аудитория и тип занятия должны существовать в БД.
- Подгруппы:
subgroupIdsразрешены только для лабораторных слотов, должны относиться к группам правила, и в одном слоте можно выбрать не больше одной подгруппы каждой группы. - Формат:
lessonFormatобязателен и хранится в слоте правила. - Жизненный цикл: архивные преподаватели, аудитории, группы и дисциплины не принимаются в новых правилах.
- Правило расписания:
status=ARCHIVEDили дата внеvalid_from/valid_toисключают правило из генерации. - Доступность аудитории:
is_available=falseзапрещает новые назначения, но не удаляет историю.
Точечные изменения расписания
Учебный отдел может создать изменение конкретной пары:
CANCEL— отменить пару;MOVE— перенести пару на другой временной слот или в другую аудиторию;REPLACE— заменить преподавателя, аудиторию или формат.
Изменения не переписывают базовое правило, а накладываются поверх сгенерированного расписания на конкретную дату.
Привязка преподаватель ↔ дисциплина
Связь Many-to-Many через таблицу teacher_subjects:
- Указывается, какие дисциплины может вести конкретный преподаватель
- Дополнительные поля:
qualification_level,experience_years
Дополнительная связь через teacher_lesson_types:
- Определяет, какие типы занятий (лекция, практика, лаба) может вести преподаватель по конкретной дисциплине
Бизнес-правила (планируемые)
Примечание: Следующие правила описаны в требованиях, но пока не полностью реализованы в коде.
Проверка конфликтов
- Критический конфликт: Преподаватель не может одновременно находиться в двух разных аудиториях
- Исключение: Преподаватель может вести несколько пар одновременно (потоковая лекция), если все группы в одной аудитории
- Вместимость: Суммарная численность всех групп в слоте не должна превышать вместимость аудитории
Управление инцидентами
- Регистрация отсутствия преподавателя (болезнь, командировка) с указанием периода
- Автоматическая подсветка конфликтующих пар (Red Zone)
- Resolution Wizard: предложение замены преподавателя или переноса занятия