подправил создание правил расписания
This commit is contained in:
@@ -191,7 +191,7 @@ public class ScheduleRuleAdminController {
|
||||
LessonType lessonType = lessonTypesRepository.findById(slotDto.lessonTypeId())
|
||||
.orElseThrow(() -> new IllegalArgumentException("Тип занятия не найден"));
|
||||
ScheduleLessonCategory category = ScheduleLessonCategory.fromLessonType(lessonType);
|
||||
Subgroup subgroup = resolveSubgroup(rule, slotDto.subgroupId(), category);
|
||||
Set<Subgroup> subgroups = resolveSubgroups(rule, slotDto, category);
|
||||
if (slotDto.lessonFormat() == null || slotDto.lessonFormat().isBlank()) {
|
||||
throw new IllegalArgumentException("Формат занятия обязателен");
|
||||
}
|
||||
@@ -201,7 +201,7 @@ public class ScheduleRuleAdminController {
|
||||
slot.setDayOfWeek(slotDto.dayOfWeek());
|
||||
slot.setParity(slotDto.parity());
|
||||
slot.setTimeSlot(timeSlot);
|
||||
slot.setSubgroup(subgroup);
|
||||
slot.setSubgroups(subgroups);
|
||||
slot.setTeacher(teacher);
|
||||
slot.setClassroom(classroom);
|
||||
slot.setLessonType(lessonType);
|
||||
@@ -209,21 +209,44 @@ public class ScheduleRuleAdminController {
|
||||
return slot;
|
||||
}
|
||||
|
||||
private Subgroup resolveSubgroup(ScheduleRule rule, Long subgroupId, ScheduleLessonCategory category) {
|
||||
if (subgroupId == null) {
|
||||
return null;
|
||||
private Set<Subgroup> resolveSubgroups(ScheduleRule rule, ScheduleRuleSlotDto slotDto, ScheduleLessonCategory category) {
|
||||
LinkedHashSet<Long> subgroupIds = new LinkedHashSet<>();
|
||||
if (slotDto.subgroupIds() != null) {
|
||||
slotDto.subgroupIds().stream()
|
||||
.filter(Objects::nonNull)
|
||||
.forEach(subgroupIds::add);
|
||||
}
|
||||
if (slotDto.subgroupId() != null) {
|
||||
subgroupIds.add(slotDto.subgroupId());
|
||||
}
|
||||
if (subgroupIds.isEmpty()) {
|
||||
return new LinkedHashSet<>();
|
||||
}
|
||||
|
||||
if (category != ScheduleLessonCategory.LABORATORY) {
|
||||
throw new IllegalArgumentException("Подгруппы можно выбирать только для лабораторных занятий");
|
||||
}
|
||||
Subgroup subgroup = subgroupRepository.findById(subgroupId)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Подгруппа не найдена"));
|
||||
boolean subgroupBelongsToRuleGroups = rule.getGroups().stream()
|
||||
.anyMatch(group -> group.getId().equals(subgroup.getStudentGroup().getId()));
|
||||
if (!subgroupBelongsToRuleGroups) {
|
||||
throw new IllegalArgumentException("Подгруппа должна относиться к одной из групп правила");
|
||||
|
||||
Set<Long> ruleGroupIds = rule.getGroups().stream()
|
||||
.map(StudentGroup::getId)
|
||||
.collect(java.util.stream.Collectors.toSet());
|
||||
Set<Long> subgroupGroupIds = new HashSet<>();
|
||||
LinkedHashSet<Subgroup> subgroups = new LinkedHashSet<>();
|
||||
|
||||
for (Long subgroupId : subgroupIds) {
|
||||
Subgroup subgroup = subgroupRepository.findById(subgroupId)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Подгруппа не найдена"));
|
||||
Long subgroupGroupId = subgroup.getStudentGroup().getId();
|
||||
if (!ruleGroupIds.contains(subgroupGroupId)) {
|
||||
throw new IllegalArgumentException("Подгруппа должна относиться к одной из групп правила");
|
||||
}
|
||||
if (!subgroupGroupIds.add(subgroupGroupId)) {
|
||||
throw new IllegalArgumentException("В одном слоте можно выбрать не больше одной подгруппы каждой группы");
|
||||
}
|
||||
subgroups.add(subgroup);
|
||||
}
|
||||
return subgroup;
|
||||
|
||||
return subgroups;
|
||||
}
|
||||
|
||||
private String validateTypeHours(ScheduleRuleDto request) {
|
||||
@@ -318,6 +341,7 @@ public class ScheduleRuleAdminController {
|
||||
|
||||
private ScheduleRuleSlotDto toSlotDto(ScheduleRuleSlot slot) {
|
||||
TimeSlot timeSlot = slot.getTimeSlot();
|
||||
List<Subgroup> sortedSubgroups = sortedSubgroups(slot);
|
||||
return new ScheduleRuleSlotDto(
|
||||
slot.getId(),
|
||||
slot.getDayOfWeek(),
|
||||
@@ -326,8 +350,10 @@ public class ScheduleRuleAdminController {
|
||||
timeSlot.getId(),
|
||||
timeSlot.getOrderNumber(),
|
||||
formatTimeSlot(timeSlot),
|
||||
slot.getSubgroupId(),
|
||||
slot.getSubgroup() == null ? null : slot.getSubgroup().getName(),
|
||||
sortedSubgroups.isEmpty() ? null : sortedSubgroups.get(0).getId(),
|
||||
sortedSubgroups.isEmpty() ? null : formatSubgroup(sortedSubgroups.get(0)),
|
||||
sortedSubgroups.stream().map(Subgroup::getId).toList(),
|
||||
sortedSubgroups.stream().map(this::formatSubgroup).toList(),
|
||||
slot.getTeacher().getId(),
|
||||
ScheduleGeneratorService.displayUserName(slot.getTeacher()),
|
||||
slot.getClassroom().getId(),
|
||||
@@ -348,4 +374,16 @@ public class ScheduleRuleAdminController {
|
||||
private String formatTimeSlot(TimeSlot timeSlot) {
|
||||
return timeSlot.getStartTime() + " - " + timeSlot.getEndTime();
|
||||
}
|
||||
|
||||
private String formatSubgroup(Subgroup subgroup) {
|
||||
return subgroup.getStudentGroup().getName() + ": " + subgroup.getName();
|
||||
}
|
||||
|
||||
private List<Subgroup> sortedSubgroups(ScheduleRuleSlot slot) {
|
||||
return slot.getSubgroups().stream()
|
||||
.sorted(Comparator
|
||||
.comparing((Subgroup subgroup) -> subgroup.getStudentGroup().getName())
|
||||
.thenComparing(Subgroup::getName))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ public record RenderedLessonDto(
|
||||
String lessonFormat,
|
||||
Long subgroupId,
|
||||
String subgroupName,
|
||||
List<Long> subgroupIds,
|
||||
List<String> subgroupNames,
|
||||
List<Long> groupIds,
|
||||
List<String> groupNames,
|
||||
String activityType,
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.magistr.app.dto;
|
||||
|
||||
import com.magistr.app.model.ScheduleParity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record ScheduleRuleSlotDto(
|
||||
Long id,
|
||||
Integer dayOfWeek,
|
||||
@@ -12,6 +14,8 @@ public record ScheduleRuleSlotDto(
|
||||
String timeSlotLabel,
|
||||
Long subgroupId,
|
||||
String subgroupName,
|
||||
List<Long> subgroupIds,
|
||||
List<String> subgroupNames,
|
||||
Long teacherId,
|
||||
String teacherName,
|
||||
Long classroomId,
|
||||
|
||||
@@ -2,6 +2,10 @@ package com.magistr.app.model;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@Entity
|
||||
@Table(name = "schedule_rule_slots")
|
||||
public class ScheduleRuleSlot {
|
||||
@@ -25,9 +29,13 @@ public class ScheduleRuleSlot {
|
||||
@JoinColumn(name = "time_slot_id", nullable = false)
|
||||
private TimeSlot timeSlot;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "subgroup_id")
|
||||
private Subgroup subgroup;
|
||||
@ManyToMany
|
||||
@JoinTable(
|
||||
name = "schedule_rule_slot_subgroups",
|
||||
joinColumns = @JoinColumn(name = "schedule_rule_slot_id"),
|
||||
inverseJoinColumns = @JoinColumn(name = "subgroup_id")
|
||||
)
|
||||
private Set<Subgroup> subgroups = new LinkedHashSet<>();
|
||||
|
||||
@ManyToOne(optional = false)
|
||||
@JoinColumn(name = "teacher_id", nullable = false)
|
||||
@@ -85,15 +93,41 @@ public class ScheduleRuleSlot {
|
||||
}
|
||||
|
||||
public Long getSubgroupId() {
|
||||
return subgroup == null ? null : subgroup.getId();
|
||||
return subgroups.stream().findFirst().map(Subgroup::getId).orElse(null);
|
||||
}
|
||||
|
||||
public Subgroup getSubgroup() {
|
||||
return subgroup;
|
||||
return subgroups.stream().findFirst().orElse(null);
|
||||
}
|
||||
|
||||
public void setSubgroup(Subgroup subgroup) {
|
||||
this.subgroup = subgroup;
|
||||
subgroups.clear();
|
||||
if (subgroup != null) {
|
||||
subgroups.add(subgroup);
|
||||
}
|
||||
}
|
||||
|
||||
public Set<Subgroup> getSubgroups() {
|
||||
return subgroups;
|
||||
}
|
||||
|
||||
public void setSubgroups(Set<Subgroup> subgroups) {
|
||||
this.subgroups.clear();
|
||||
if (subgroups != null) {
|
||||
this.subgroups.addAll(subgroups);
|
||||
}
|
||||
}
|
||||
|
||||
public List<Long> getSubgroupIds() {
|
||||
return subgroups.stream()
|
||||
.map(Subgroup::getId)
|
||||
.toList();
|
||||
}
|
||||
|
||||
public List<String> getSubgroupNames() {
|
||||
return subgroups.stream()
|
||||
.map(Subgroup::getName)
|
||||
.toList();
|
||||
}
|
||||
|
||||
public User getTeacher() {
|
||||
|
||||
@@ -19,8 +19,8 @@ public interface ScheduleRuleRepository extends JpaRepository<ScheduleRule, Long
|
||||
left join fetch r.groups groups
|
||||
left join fetch r.slots slots
|
||||
left join fetch slots.timeSlot
|
||||
left join fetch slots.subgroup subgroup
|
||||
left join fetch subgroup.studentGroup
|
||||
left join fetch slots.subgroups slotSubgroups
|
||||
left join fetch slotSubgroups.studentGroup
|
||||
left join fetch slots.teacher
|
||||
left join fetch slots.classroom
|
||||
left join fetch slots.lessonType
|
||||
@@ -42,8 +42,8 @@ public interface ScheduleRuleRepository extends JpaRepository<ScheduleRule, Long
|
||||
left join fetch r.groups groups
|
||||
left join fetch r.slots slots
|
||||
left join fetch slots.timeSlot
|
||||
left join fetch slots.subgroup subgroup
|
||||
left join fetch subgroup.studentGroup
|
||||
left join fetch slots.subgroups slotSubgroups
|
||||
left join fetch slotSubgroups.studentGroup
|
||||
left join fetch slots.teacher
|
||||
left join fetch slots.classroom
|
||||
left join fetch slots.lessonType
|
||||
@@ -64,8 +64,8 @@ public interface ScheduleRuleRepository extends JpaRepository<ScheduleRule, Long
|
||||
left join fetch r.groups groups
|
||||
left join fetch r.slots slots
|
||||
left join fetch slots.timeSlot
|
||||
left join fetch slots.subgroup subgroup
|
||||
left join fetch subgroup.studentGroup
|
||||
left join fetch slots.subgroups slotSubgroups
|
||||
left join fetch slotSubgroups.studentGroup
|
||||
left join fetch slots.teacher
|
||||
left join fetch slots.classroom
|
||||
left join fetch slots.lessonType
|
||||
@@ -82,8 +82,8 @@ public interface ScheduleRuleRepository extends JpaRepository<ScheduleRule, Long
|
||||
left join fetch r.groups groups
|
||||
left join fetch r.slots slots
|
||||
left join fetch slots.timeSlot
|
||||
left join fetch slots.subgroup subgroup
|
||||
left join fetch subgroup.studentGroup
|
||||
left join fetch slots.subgroups slotSubgroups
|
||||
left join fetch slotSubgroups.studentGroup
|
||||
left join fetch slots.teacher
|
||||
left join fetch slots.classroom
|
||||
left join fetch slots.lessonType
|
||||
|
||||
@@ -7,6 +7,6 @@ import org.springframework.data.repository.query.Param;
|
||||
|
||||
public interface ScheduleRuleSlotRepository extends JpaRepository<ScheduleRuleSlot, Long> {
|
||||
|
||||
@Query("select case when count(slot) > 0 then true else false end from ScheduleRuleSlot slot where slot.subgroup.id = :subgroupId")
|
||||
@Query("select case when count(slot) > 0 then true else false end from ScheduleRuleSlot slot join slot.subgroups subgroup where subgroup.id = :subgroupId")
|
||||
boolean existsBySubgroupId(@Param("subgroupId") Long subgroupId);
|
||||
}
|
||||
|
||||
@@ -201,22 +201,54 @@ public class ScheduleGeneratorService {
|
||||
if (totalHours <= 0) {
|
||||
continue;
|
||||
}
|
||||
List<StudentGroup> lessonGroups = groupsForSlot(slot, eligibleGroups);
|
||||
if (lessonGroups.isEmpty()) {
|
||||
List<LessonScope> lessonScopes = lessonScopesForSlot(slot, category, eligibleGroups);
|
||||
if (lessonScopes.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
String consumptionBucket = consumptionBucket(category, slot);
|
||||
int consumedHours = consumedHoursByBucket.computeIfAbsent(
|
||||
consumptionBucket,
|
||||
key -> calculateConsumedHoursBeforeDate(rule, targetGroup, date, category, slot.getSubgroupId())
|
||||
);
|
||||
if (consumedHours >= totalHours) {
|
||||
|
||||
List<ActiveLessonScope> activeScopes = new ArrayList<>();
|
||||
for (LessonScope scope : lessonScopes) {
|
||||
String consumptionBucket = consumptionBucket(category, scope.subgroupId());
|
||||
int consumedHours = consumedHoursByBucket.computeIfAbsent(
|
||||
consumptionBucket,
|
||||
key -> calculateConsumedHoursBeforeDate(rule, targetGroup, date, category, scope.subgroupId())
|
||||
);
|
||||
if (consumedHours >= totalHours) {
|
||||
continue;
|
||||
}
|
||||
int remainingAfterLesson = Math.max(0, totalHours - consumedHours - ACADEMIC_HOURS_PER_SLOT);
|
||||
activeScopes.add(new ActiveLessonScope(scope, consumedHours, remainingAfterLesson));
|
||||
}
|
||||
|
||||
if (activeScopes.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
int remainingAfterLesson = Math.max(0, totalHours - consumedHours - ACADEMIC_HOURS_PER_SLOT);
|
||||
result.add(toRenderedLesson(rule, slot, date, weekNumber, dateParity, lessonGroups,
|
||||
|
||||
Map<Long, StudentGroup> lessonGroupById = new LinkedHashMap<>();
|
||||
Map<Long, Subgroup> lessonSubgroupById = new LinkedHashMap<>();
|
||||
activeScopes.forEach(scope -> {
|
||||
scope.scope().groups().forEach(group -> lessonGroupById.putIfAbsent(group.getId(), group));
|
||||
scope.scope().subgroups().forEach(subgroup -> lessonSubgroupById.putIfAbsent(subgroup.getId(), subgroup));
|
||||
});
|
||||
List<StudentGroup> lessonGroups = new ArrayList<>(lessonGroupById.values());
|
||||
List<Subgroup> lessonSubgroups = new ArrayList<>(lessonSubgroupById.values());
|
||||
int consumedHours = activeScopes.stream()
|
||||
.mapToInt(ActiveLessonScope::consumedHours)
|
||||
.min()
|
||||
.orElse(0);
|
||||
int remainingAfterLesson = activeScopes.stream()
|
||||
.mapToInt(ActiveLessonScope::remainingAfterLesson)
|
||||
.min()
|
||||
.orElse(0);
|
||||
|
||||
result.add(toRenderedLesson(rule, slot, date, weekNumber, dateParity, lessonGroups, lessonSubgroups,
|
||||
totalHours, consumedHours, remainingAfterLesson));
|
||||
consumedHoursByBucket.put(consumptionBucket, consumedHours + ACADEMIC_HOURS_PER_SLOT);
|
||||
for (ActiveLessonScope scope : activeScopes) {
|
||||
consumedHoursByBucket.put(
|
||||
consumptionBucket(category, scope.scope().subgroupId()),
|
||||
scope.consumedHours() + ACADEMIC_HOURS_PER_SLOT
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,8 +304,8 @@ public class ScheduleGeneratorService {
|
||||
.filter(slot -> Objects.equals(slot.getDayOfWeek(), dayOfWeek))
|
||||
.filter(slot -> slot.getParity() == ScheduleParity.BOTH || slot.getParity() == dateParity)
|
||||
.filter(slot -> ScheduleLessonCategory.fromLessonType(slot.getLessonType()) == category)
|
||||
.filter(slot -> Objects.equals(slot.getSubgroupId(), subgroupId))
|
||||
.filter(slot -> !groupsForSlot(slot, eligibleGroups).isEmpty())
|
||||
.flatMap(slot -> lessonScopesForSlot(slot, category, eligibleGroups).stream())
|
||||
.filter(scope -> Objects.equals(scope.subgroupId(), subgroupId))
|
||||
.count();
|
||||
consumed += (int) activeSlots * ACADEMIC_HOURS_PER_SLOT;
|
||||
if (consumed >= rule.academicHoursFor(category)) {
|
||||
@@ -283,26 +315,51 @@ public class ScheduleGeneratorService {
|
||||
return consumed;
|
||||
}
|
||||
|
||||
private String consumptionBucket(ScheduleLessonCategory category, ScheduleRuleSlot slot) {
|
||||
return category.name() + ":" + (slot.getSubgroupId() == null ? "whole" : slot.getSubgroupId());
|
||||
private String consumptionBucket(ScheduleLessonCategory category, Long subgroupId) {
|
||||
return category.name() + ":" + (subgroupId == null ? "whole" : subgroupId);
|
||||
}
|
||||
|
||||
private List<StudentGroup> groupsForSlot(ScheduleRuleSlot slot, List<StudentGroup> eligibleGroups) {
|
||||
if (slot.getSubgroup() == null) {
|
||||
return eligibleGroups;
|
||||
private List<LessonScope> lessonScopesForSlot(ScheduleRuleSlot slot,
|
||||
ScheduleLessonCategory category,
|
||||
List<StudentGroup> eligibleGroups) {
|
||||
if (category != ScheduleLessonCategory.LABORATORY || slot.getSubgroups().isEmpty()) {
|
||||
return eligibleGroups.isEmpty()
|
||||
? List.of()
|
||||
: List.of(new LessonScope(eligibleGroups, List.of(), null));
|
||||
}
|
||||
Long subgroupGroupId = slot.getSubgroup().getStudentGroup().getId();
|
||||
return eligibleGroups.stream()
|
||||
.filter(group -> Objects.equals(group.getId(), subgroupGroupId))
|
||||
|
||||
Set<Long> eligibleGroupIds = eligibleGroups.stream()
|
||||
.map(StudentGroup::getId)
|
||||
.collect(Collectors.toSet());
|
||||
Map<Long, StudentGroup> eligibleGroupById = eligibleGroups.stream()
|
||||
.collect(Collectors.toMap(StudentGroup::getId, group -> group));
|
||||
|
||||
return slot.getSubgroups().stream()
|
||||
.filter(subgroup -> eligibleGroupIds.contains(subgroup.getStudentGroup().getId()))
|
||||
.sorted(Comparator
|
||||
.comparing((Subgroup subgroup) -> subgroup.getStudentGroup().getName())
|
||||
.thenComparing(Subgroup::getName))
|
||||
.map(subgroup -> new LessonScope(
|
||||
List.of(eligibleGroupById.get(subgroup.getStudentGroup().getId())),
|
||||
List.of(subgroup),
|
||||
subgroup.getId()
|
||||
))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private record LessonScope(List<StudentGroup> groups, List<Subgroup> subgroups, Long subgroupId) {
|
||||
}
|
||||
|
||||
private record ActiveLessonScope(LessonScope scope, int consumedHours, int remainingAfterLesson) {
|
||||
}
|
||||
|
||||
private RenderedLessonDto toRenderedLesson(ScheduleRule rule,
|
||||
ScheduleRuleSlot slot,
|
||||
LocalDate date,
|
||||
int weekNumber,
|
||||
ScheduleParity dateParity,
|
||||
List<StudentGroup> groups,
|
||||
List<Subgroup> subgroups,
|
||||
int lessonTypeAcademicHours,
|
||||
int consumedBeforeLesson,
|
||||
int remainingAfterLesson) {
|
||||
@@ -310,6 +367,11 @@ public class ScheduleGeneratorService {
|
||||
List<StudentGroup> sortedGroups = groups.stream()
|
||||
.sorted(Comparator.comparing(StudentGroup::getName))
|
||||
.toList();
|
||||
List<Subgroup> sortedSubgroups = subgroups.stream()
|
||||
.sorted(Comparator
|
||||
.comparing((Subgroup subgroup) -> subgroup.getStudentGroup().getName())
|
||||
.thenComparing(Subgroup::getName))
|
||||
.toList();
|
||||
|
||||
return new RenderedLessonDto(
|
||||
rule.getId(),
|
||||
@@ -332,8 +394,10 @@ public class ScheduleGeneratorService {
|
||||
slot.getLessonType().getId(),
|
||||
slot.getLessonType().getLessonType(),
|
||||
slot.getLessonFormat(),
|
||||
slot.getSubgroupId(),
|
||||
slot.getSubgroup() == null ? null : slot.getSubgroup().getName(),
|
||||
sortedSubgroups.size() == 1 ? sortedSubgroups.get(0).getId() : null,
|
||||
sortedSubgroups.size() == 1 ? formatSubgroup(sortedSubgroups.get(0)) : null,
|
||||
sortedSubgroups.stream().map(Subgroup::getId).toList(),
|
||||
sortedSubgroups.stream().map(this::formatSubgroup).toList(),
|
||||
sortedGroups.stream().map(StudentGroup::getId).toList(),
|
||||
sortedGroups.stream().map(StudentGroup::getName).collect(Collectors.toList()),
|
||||
"Т",
|
||||
@@ -343,6 +407,10 @@ public class ScheduleGeneratorService {
|
||||
);
|
||||
}
|
||||
|
||||
private String formatSubgroup(Subgroup subgroup) {
|
||||
return subgroup.getStudentGroup().getName() + ": " + subgroup.getName();
|
||||
}
|
||||
|
||||
private TimeSlot resolveEffectiveTimeSlot(TimeSlot baseSlot, LocalDate date) {
|
||||
if (baseSlot == null || baseSlot.getOrderNumber() == null) {
|
||||
return baseSlot;
|
||||
|
||||
@@ -515,7 +515,6 @@ CREATE TABLE IF NOT EXISTS schedule_rule_slots (
|
||||
day_of_week INT NOT NULL,
|
||||
parity VARCHAR(10) NOT NULL,
|
||||
time_slot_id BIGINT NOT NULL REFERENCES time_slots(id),
|
||||
subgroup_id BIGINT NULL REFERENCES subgroups(id) ON DELETE SET NULL,
|
||||
teacher_id BIGINT NOT NULL REFERENCES users(id),
|
||||
classroom_id BIGINT NOT NULL REFERENCES classrooms(id),
|
||||
lesson_type_id BIGINT NOT NULL REFERENCES lesson_types(id),
|
||||
@@ -528,34 +527,84 @@ CREATE TABLE IF NOT EXISTS schedule_rule_slots (
|
||||
CREATE INDEX IF NOT EXISTS idx_schedule_rule_slots_rule ON schedule_rule_slots(schedule_rule_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_schedule_rule_slots_teacher ON schedule_rule_slots(teacher_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_schedule_rule_slots_classroom ON schedule_rule_slots(classroom_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_schedule_rule_slots_subgroup ON schedule_rule_slots(subgroup_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_schedule_rule_slots_time ON schedule_rule_slots(day_of_week, parity, time_slot_id);
|
||||
|
||||
CREATE OR REPLACE FUNCTION validate_schedule_rule_slot_subgroup()
|
||||
CREATE TABLE IF NOT EXISTS schedule_rule_slot_subgroups (
|
||||
schedule_rule_slot_id BIGINT NOT NULL REFERENCES schedule_rule_slots(id) ON DELETE CASCADE,
|
||||
subgroup_id BIGINT NOT NULL REFERENCES subgroups(id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (schedule_rule_slot_id, subgroup_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_schedule_rule_slot_subgroups_subgroup ON schedule_rule_slot_subgroups(subgroup_id);
|
||||
|
||||
CREATE OR REPLACE FUNCTION validate_schedule_rule_slot_subgroups()
|
||||
RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
lesson_type_name VARCHAR(50);
|
||||
rule_id BIGINT;
|
||||
subgroup_group_id BIGINT;
|
||||
has_group_duplicate BOOLEAN;
|
||||
BEGIN
|
||||
IF NEW.subgroup_id IS NULL THEN
|
||||
RETURN NEW;
|
||||
END IF;
|
||||
|
||||
SELECT name INTO lesson_type_name
|
||||
FROM lesson_types
|
||||
WHERE id = NEW.lesson_type_id;
|
||||
SELECT slot.schedule_rule_id, type.name INTO rule_id, lesson_type_name
|
||||
FROM schedule_rule_slots slot
|
||||
JOIN lesson_types type ON type.id = slot.lesson_type_id
|
||||
WHERE slot.id = NEW.schedule_rule_slot_id;
|
||||
|
||||
IF lesson_type_name IS NULL OR LOWER(lesson_type_name) NOT LIKE '%лаб%' THEN
|
||||
RAISE EXCEPTION 'Подгруппы можно выбирать только для лабораторных занятий';
|
||||
END IF;
|
||||
|
||||
SELECT group_id INTO subgroup_group_id
|
||||
FROM subgroups
|
||||
WHERE id = NEW.subgroup_id;
|
||||
|
||||
IF subgroup_group_id IS NULL THEN
|
||||
RAISE EXCEPTION 'Подгруппа не найдена';
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM schedule_rule_groups rule_group
|
||||
WHERE rule_group.schedule_rule_id = rule_id
|
||||
AND rule_group.group_id = subgroup_group_id
|
||||
) THEN
|
||||
RAISE EXCEPTION 'Подгруппа должна относиться к одной из групп правила';
|
||||
END IF;
|
||||
|
||||
IF TG_OP = 'UPDATE' THEN
|
||||
SELECT EXISTS (
|
||||
SELECT 1
|
||||
FROM schedule_rule_slot_subgroups slot_subgroup
|
||||
JOIN subgroups subgroup ON subgroup.id = slot_subgroup.subgroup_id
|
||||
WHERE slot_subgroup.schedule_rule_slot_id = NEW.schedule_rule_slot_id
|
||||
AND subgroup.group_id = subgroup_group_id
|
||||
AND NOT (
|
||||
slot_subgroup.schedule_rule_slot_id = OLD.schedule_rule_slot_id
|
||||
AND slot_subgroup.subgroup_id = OLD.subgroup_id
|
||||
)
|
||||
) INTO has_group_duplicate;
|
||||
ELSE
|
||||
SELECT EXISTS (
|
||||
SELECT 1
|
||||
FROM schedule_rule_slot_subgroups slot_subgroup
|
||||
JOIN subgroups subgroup ON subgroup.id = slot_subgroup.subgroup_id
|
||||
WHERE slot_subgroup.schedule_rule_slot_id = NEW.schedule_rule_slot_id
|
||||
AND subgroup.group_id = subgroup_group_id
|
||||
) INTO has_group_duplicate;
|
||||
END IF;
|
||||
|
||||
IF has_group_duplicate THEN
|
||||
RAISE EXCEPTION 'В одном слоте можно выбрать не больше одной подгруппы каждой группы';
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER validate_schedule_rule_slot_subgroup
|
||||
BEFORE INSERT OR UPDATE ON schedule_rule_slots
|
||||
CREATE TRIGGER validate_schedule_rule_slot_subgroups
|
||||
BEFORE INSERT OR UPDATE ON schedule_rule_slot_subgroups
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION validate_schedule_rule_slot_subgroup();
|
||||
EXECUTE FUNCTION validate_schedule_rule_slot_subgroups();
|
||||
|
||||
CREATE TEMP TABLE tmp_default_rules AS
|
||||
SELECT
|
||||
@@ -712,6 +761,7 @@ COMMENT ON TABLE student_group_calendar_assignments IS 'Назначения к
|
||||
COMMENT ON TABLE schedule_rules IS 'Правила динамической генерации расписания';
|
||||
COMMENT ON TABLE schedule_rule_groups IS 'Привязка правил расписания к учебным группам';
|
||||
COMMENT ON TABLE schedule_rule_slots IS 'Слоты проведения занятий внутри правила расписания';
|
||||
COMMENT ON TABLE schedule_rule_slot_subgroups IS 'Подгруппы лабораторных слотов правила расписания';
|
||||
COMMENT ON COLUMN schedule_rules.lecture_academic_hours IS 'Лимит академических часов лекций';
|
||||
COMMENT ON COLUMN schedule_rules.laboratory_academic_hours IS 'Лимит академических часов лабораторных работ';
|
||||
COMMENT ON COLUMN schedule_rules.practice_academic_hours IS 'Лимит академических часов практик';
|
||||
|
||||
Reference in New Issue
Block a user