Исправил косяки в модалках решения конфликтов правил

This commit is contained in:
dipatrik10
2026-06-03 21:22:24 +03:00
parent 53298d9d07
commit 3d50b6b006
7 changed files with 264 additions and 69 deletions

View File

@@ -89,12 +89,9 @@ public class ScheduleRuleAdminController {
try {
ScheduleRule rule = new ScheduleRule();
applyRule(rule, request);
ScheduleRule conflictRule = findConflictingRule(request, rule.getSlots(), null);
if (conflictRule != null) {
return ResponseEntity.status(HttpStatus.CONFLICT).body(Map.of(
"message", "Невозможно сохранить правило: слот занят",
"conflictRule", toDto(conflictRule)
));
ScheduleRuleConflict conflict = findConflict(request, rule.getSlots(), null);
if (conflict != null) {
return conflictResponse(conflict);
}
ScheduleRule saved = scheduleRuleRepository.save(rule);
scheduleGeneratorService.clearCache();
@@ -120,12 +117,9 @@ public class ScheduleRuleAdminController {
try {
ScheduleRule candidate = new ScheduleRule();
applyRule(candidate, request);
ScheduleRule conflictRule = findConflictingRule(request, candidate.getSlots(), rule.getId());
if (conflictRule != null) {
return ResponseEntity.status(HttpStatus.CONFLICT).body(Map.of(
"message", "Невозможно сохранить правило: слот занят",
"conflictRule", toDto(conflictRule)
));
ScheduleRuleConflict conflict = findConflict(request, candidate.getSlots(), rule.getId());
if (conflict != null) {
return conflictResponse(conflict);
}
applyRule(rule, request);
ScheduleRule saved = scheduleRuleRepository.save(rule);
@@ -205,34 +199,82 @@ public class ScheduleRuleAdminController {
rule.getSlots().addAll(slots);
}
private ScheduleRule findConflictingRule(ScheduleRuleDto request, Collection<ScheduleRuleSlot> newSlots, Long excludeRuleId) {
private ResponseEntity<?> conflictResponse(ScheduleRuleConflict conflict) {
return ResponseEntity.status(HttpStatus.CONFLICT).body(Map.of(
"message", "Невозможно сохранить правило: слот занят",
"conflictRule", toDto(conflict.rule()),
"conflictFields", conflict.fields().stream().toList(),
"conflictReasons", conflict.fields().stream().map(this::conflictFieldLabel).toList()
));
}
private String conflictFieldLabel(String field) {
return switch (field) {
case "teacher" -> "Преподаватель";
case "classroom" -> "Аудитория";
case "group" -> "Группа";
default -> field;
};
}
private ScheduleRuleConflict findConflict(ScheduleRuleDto request, Collection<ScheduleRuleSlot> newSlots, Long excludeRuleId) {
Map<ScheduleRuleSlot, Set<Integer>> newActiveWeeks = activeWeeksBySlot(newSlots);
return scheduleRuleRepository.findAllWithDetails().stream()
.filter(rule -> !Objects.equals(rule.getId(), excludeRuleId))
.filter(rule -> rule.getSemester() != null && Objects.equals(rule.getSemester().getId(), request.semesterId()))
.filter(rule -> hasSlotConflict(newSlots, rule.getSlots()))
.min(Comparator.comparing(ScheduleRule::getId))
.map(rule -> toConflict(rule, newSlots, newActiveWeeks, rule.getSlots()))
.filter(Objects::nonNull)
.min(Comparator.comparing(conflict -> conflict.rule().getId()))
.orElse(null);
}
private boolean hasSlotConflict(Collection<ScheduleRuleSlot> newSlots, Collection<ScheduleRuleSlot> existingSlots) {
Map<ScheduleRuleSlot, Set<Integer>> newActiveWeeks = activeWeeksBySlot(newSlots);
private ScheduleRuleConflict toConflict(ScheduleRule existingRule,
Collection<ScheduleRuleSlot> newSlots,
Map<ScheduleRuleSlot, Set<Integer>> newActiveWeeks,
Collection<ScheduleRuleSlot> existingSlots) {
Map<ScheduleRuleSlot, Set<Integer>> existingActiveWeeks = activeWeeksBySlot(existingSlots);
LinkedHashSet<String> fields = new LinkedHashSet<>();
for (ScheduleRuleSlot newSlot : newSlots) {
for (ScheduleRuleSlot existingSlot : existingSlots) {
if (sameTimeSlot(newSlot, existingSlot)
&& activeWeeksOverlap(newActiveWeeks.get(newSlot), existingActiveWeeks.get(existingSlot))
&& sameTeacherOrClassroomOrAudience(newSlot, existingSlot)) {
return true;
&& activeWeeksOverlap(newActiveWeeks.get(newSlot), existingActiveWeeks.get(existingSlot))) {
fields.addAll(conflictFields(newSlot, existingSlot));
}
}
}
return false;
return fields.isEmpty() ? null : new ScheduleRuleConflict(existingRule, fields);
}
private Set<String> conflictFields(ScheduleRuleSlot first, ScheduleRuleSlot second) {
LinkedHashSet<String> fields = new LinkedHashSet<>();
if (Objects.equals(teacherId(first), teacherId(second))) {
fields.add("teacher");
}
if (Objects.equals(classroomId(first), classroomId(second))) {
fields.add("classroom");
}
if (sameAudience(first, second)) {
fields.add("group");
}
return fields;
}
private boolean sameTimeSlot(ScheduleRuleSlot first, ScheduleRuleSlot second) {
return Objects.equals(first.getDayOfWeek(), second.getDayOfWeek())
&& parityOverlaps(first.getParity(), second.getParity())
&& Objects.equals(first.getTimeSlot().getId(), second.getTimeSlot().getId());
&& Objects.equals(timeSlotId(first), timeSlotId(second));
}
private Long teacherId(ScheduleRuleSlot slot) {
return slot.getTeacher() == null ? null : slot.getTeacher().getId();
}
private Long classroomId(ScheduleRuleSlot slot) {
return slot.getClassroom() == null ? null : slot.getClassroom().getId();
}
private Long timeSlotId(ScheduleRuleSlot slot) {
return slot.getTimeSlot() == null ? null : slot.getTimeSlot().getId();
}
private boolean parityOverlaps(ScheduleParity first, ScheduleParity second) {
@@ -242,12 +284,6 @@ public class ScheduleRuleAdminController {
return first == ScheduleParity.BOTH || second == ScheduleParity.BOTH;
}
private boolean sameTeacherOrClassroomOrAudience(ScheduleRuleSlot first, ScheduleRuleSlot second) {
return Objects.equals(first.getTeacher().getId(), second.getTeacher().getId())
|| Objects.equals(first.getClassroom().getId(), second.getClassroom().getId())
|| sameAudience(first, second);
}
private boolean sameAudience(ScheduleRuleSlot first, ScheduleRuleSlot second) {
Set<Long> firstGroupIds = groupIds(first);
Set<Long> secondGroupIds = groupIds(second);
@@ -622,4 +658,7 @@ public class ScheduleRuleAdminController {
.thenComparing(Subgroup::getName))
.toList();
}
private record ScheduleRuleConflict(ScheduleRule rule, LinkedHashSet<String> fields) {
}
}