баг-фикс завершён
This commit is contained in:
@@ -38,6 +38,10 @@ final class DatabaseConstraintViolationMapper {
|
||||
badRequest("chk_calendar_days_week_positive", "Номер недели должен быть положительным"),
|
||||
badRequest("chk_calendar_days_day", "День недели должен быть от 1 до 7"),
|
||||
badRequest("chk_academic_calendar_subjects_semester_positive", "Номер семестра должен быть положительным"),
|
||||
badRequest("chk_student_groups_group_size_positive", "Численность группы должна быть больше нуля"),
|
||||
badRequest("chk_student_groups_year_start_positive", "Год начала обучения должен быть больше нуля"),
|
||||
badRequest("chk_subgroups_student_capacity_positive", "Численность подгруппы должна быть больше нуля"),
|
||||
badRequest("chk_student_group_subgroup_capacity", "Сумма численностей активных подгрупп не может превышать численность группы"),
|
||||
badRequest("chk_schedule_rules_type_hours_non_negative", "Количество академических часов не может быть отрицательным"),
|
||||
badRequest("chk_schedule_rules_has_type_hours", "В правиле должен быть хотя бы один тип занятия с часами"),
|
||||
badRequest("chk_schedule_rules_start_weeks_positive", "Неделя начала занятия должна быть положительной"),
|
||||
|
||||
@@ -6,7 +6,6 @@ import com.magistr.app.dto.CreateGroupRequest;
|
||||
import com.magistr.app.dto.GroupCalendarAssignmentDto;
|
||||
import com.magistr.app.dto.GroupResponse;
|
||||
import com.magistr.app.model.AcademicCalendarSubject;
|
||||
import com.magistr.app.model.EducationForm;
|
||||
import com.magistr.app.model.Role;
|
||||
import com.magistr.app.model.Speciality;
|
||||
import com.magistr.app.model.SpecialtyProfile;
|
||||
@@ -31,7 +30,6 @@ import java.time.LocalDate;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -43,9 +41,6 @@ public class GroupController {
|
||||
private static final Logger logger = LoggerFactory.getLogger(GroupController.class);
|
||||
|
||||
private final GroupRepository groupRepository;
|
||||
private final EducationFormRepository educationFormRepository;
|
||||
private final SpecialtiesRepository specialtiesRepository;
|
||||
private final SpecialtyProfileRepository specialtyProfileRepository;
|
||||
private final AcademicCalendarSubjectRepository calendarSubjectRepository;
|
||||
private final StudentGroupCalendarAssignmentRepository assignmentRepository;
|
||||
private final ScheduleGeneratorService scheduleGeneratorService;
|
||||
@@ -54,25 +49,18 @@ public class GroupController {
|
||||
private final BusinessTimeService businessTime;
|
||||
|
||||
public GroupController(GroupRepository groupRepository,
|
||||
EducationFormRepository educationFormRepository,
|
||||
SpecialtiesRepository specialtiesRepository,
|
||||
SpecialtyProfileRepository specialtyProfileRepository,
|
||||
AcademicCalendarSubjectRepository calendarSubjectRepository,
|
||||
StudentGroupCalendarAssignmentRepository assignmentRepository,
|
||||
ScheduleGeneratorService scheduleGeneratorService,
|
||||
StudentGroupLifecycleService groupLifecycleService,
|
||||
AcademicStructureService academicStructureService) {
|
||||
this(groupRepository, educationFormRepository, specialtiesRepository,
|
||||
specialtyProfileRepository, calendarSubjectRepository, assignmentRepository,
|
||||
this(groupRepository, calendarSubjectRepository, assignmentRepository,
|
||||
scheduleGeneratorService, groupLifecycleService, academicStructureService,
|
||||
BusinessTimeService.systemDefault());
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public GroupController(GroupRepository groupRepository,
|
||||
EducationFormRepository educationFormRepository,
|
||||
SpecialtiesRepository specialtiesRepository,
|
||||
SpecialtyProfileRepository specialtyProfileRepository,
|
||||
AcademicCalendarSubjectRepository calendarSubjectRepository,
|
||||
StudentGroupCalendarAssignmentRepository assignmentRepository,
|
||||
ScheduleGeneratorService scheduleGeneratorService,
|
||||
@@ -80,9 +68,6 @@ public class GroupController {
|
||||
AcademicStructureService academicStructureService,
|
||||
BusinessTimeService businessTime) {
|
||||
this.groupRepository = groupRepository;
|
||||
this.educationFormRepository = educationFormRepository;
|
||||
this.specialtiesRepository = specialtiesRepository;
|
||||
this.specialtyProfileRepository = specialtyProfileRepository;
|
||||
this.calendarSubjectRepository = calendarSubjectRepository;
|
||||
this.assignmentRepository = assignmentRepository;
|
||||
this.scheduleGeneratorService = scheduleGeneratorService;
|
||||
@@ -144,46 +129,10 @@ public class GroupController {
|
||||
@PostMapping
|
||||
@RequireRoles({Role.ADMIN})
|
||||
public ResponseEntity<?> createGroup(@RequestBody CreateGroupRequest request) {
|
||||
logger.info("Получен запрос на создание новой группы: name = {}, groupSize = {}, educationFormId = {}, departmentId = {}, yearStartStudy = {}",
|
||||
request.getName(), request.getGroupSize(), request.getEducationFormId(), request.getDepartmentId(), request.getYearStartStudy());
|
||||
try {
|
||||
ResponseEntity<?> validationError = validateGroupRequest(request);
|
||||
if (validationError != null) {
|
||||
return validationError;
|
||||
}
|
||||
|
||||
Optional<EducationForm> efOpt = educationFormRepository.findById(request.getEducationFormId());
|
||||
if (efOpt.isEmpty()) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", "Форма обучения не найдена"));
|
||||
}
|
||||
Speciality speciality = specialtiesRepository.findById(request.getEffectiveSpecialtyId())
|
||||
.orElse(null);
|
||||
if (speciality == null) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", "Специальность не найдена"));
|
||||
}
|
||||
SpecialtyProfile profile = specialtyProfileRepository.findById(request.getSpecialtyProfileId())
|
||||
.orElse(null);
|
||||
if (profile == null || !profile.getSpeciality().getId().equals(speciality.getId())) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", "Профиль обучения не найден для выбранной специальности"));
|
||||
}
|
||||
|
||||
StudentGroup group = new StudentGroup();
|
||||
group.setName(request.getName().trim());
|
||||
group.setGroupSize(request.getGroupSize());
|
||||
group.setEducationForm(efOpt.get());
|
||||
group.setDepartmentId(request.getDepartmentId());
|
||||
group.setYearStartStudy(request.getYearStartStudy());
|
||||
group.setSpeciality(speciality);
|
||||
group.setSpecialtyProfile(profile);
|
||||
groupRepository.save(group);
|
||||
|
||||
logger.info("Группа успешно создана с ID - {}", group.getId());
|
||||
|
||||
return ResponseEntity.ok(mapToResponse(group));
|
||||
} catch (Exception e) {
|
||||
logger.error("Ошибка при создании группы", e);
|
||||
throw e;
|
||||
}
|
||||
logger.info("Получен запрос на создание новой группы");
|
||||
StudentGroup group = academicStructureService.createGroup(request);
|
||||
logger.info("Группа успешно создана с ID - {}", group.getId());
|
||||
return ResponseEntity.ok(mapToResponse(group));
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
@@ -222,36 +171,6 @@ public class GroupController {
|
||||
return ResponseEntity.ok(mapToResponse(group));
|
||||
}
|
||||
|
||||
private ResponseEntity<?> validateGroupRequest(CreateGroupRequest request) {
|
||||
if (request.getName() == null || request.getName().isBlank()) {
|
||||
return validationError("Название группы обязательно");
|
||||
}
|
||||
if (request.getGroupSize() == null) {
|
||||
return validationError("Численность группы обязательна");
|
||||
}
|
||||
if (request.getEducationFormId() == null) {
|
||||
return validationError("Форма обучения обязательна");
|
||||
}
|
||||
if (request.getDepartmentId() == null || request.getDepartmentId() == 0) {
|
||||
return validationError("ID кафедры обязателен");
|
||||
}
|
||||
if (request.getYearStartStudy() == null || request.getYearStartStudy() == 0) {
|
||||
return validationError("Год начала обучения обязателен");
|
||||
}
|
||||
if (request.getEffectiveSpecialtyId() == null || request.getEffectiveSpecialtyId() == 0) {
|
||||
return validationError("Код специальности обязателен");
|
||||
}
|
||||
if (request.getSpecialtyProfileId() == null || request.getSpecialtyProfileId() == 0) {
|
||||
return validationError("Профиль обучения обязателен");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private ResponseEntity<?> validationError(String errorMessage) {
|
||||
logger.error("Ошибка валидации: {}", errorMessage);
|
||||
return ResponseEntity.badRequest().body(Map.of("message", errorMessage));
|
||||
}
|
||||
|
||||
private GroupResponse mapToResponse(StudentGroup group) {
|
||||
LocalDate today = businessTime.today();
|
||||
int course = CourseAndSemesterCalculator.getActualCourse(group.getYearStartStudy(), today);
|
||||
|
||||
@@ -10,6 +10,7 @@ import com.magistr.app.repository.ScheduleRuleSlotRepository;
|
||||
import com.magistr.app.repository.SubgroupRepository;
|
||||
import com.magistr.app.service.ScheduleGeneratorService;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
@@ -53,8 +54,9 @@ public class SubgroupController {
|
||||
|
||||
@PostMapping("/api/groups/{groupId}/subgroups")
|
||||
@RequireRoles({Role.ADMIN, Role.EDUCATION_OFFICE})
|
||||
@Transactional
|
||||
public ResponseEntity<?> create(@PathVariable Long groupId, @RequestBody SubgroupDto request) {
|
||||
StudentGroup group = groupRepository.findById(groupId).orElse(null);
|
||||
StudentGroup group = groupRepository.findByIdForUpdate(groupId).orElse(null);
|
||||
if (group == null) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
@@ -81,9 +83,14 @@ public class SubgroupController {
|
||||
|
||||
@PutMapping("/api/groups/{groupId}/subgroups/{id}")
|
||||
@RequireRoles({Role.ADMIN, Role.EDUCATION_OFFICE})
|
||||
@Transactional
|
||||
public ResponseEntity<?> update(@PathVariable Long groupId,
|
||||
@PathVariable Long id,
|
||||
@RequestBody SubgroupDto request) {
|
||||
StudentGroup group = groupRepository.findByIdForUpdate(groupId).orElse(null);
|
||||
if (group == null) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
Subgroup subgroup = subgroupRepository.findByIdAndStudentGroupId(id, groupId).orElse(null);
|
||||
if (subgroup == null) {
|
||||
return ResponseEntity.notFound().build();
|
||||
@@ -98,7 +105,7 @@ public class SubgroupController {
|
||||
if (duplicateName) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", "Подгруппа с таким названием уже есть в группе"));
|
||||
}
|
||||
String capacityError = validateGroupCapacity(subgroup.getStudentGroup(), id, request.studentCapacity());
|
||||
String capacityError = validateGroupCapacity(group, id, request.studentCapacity());
|
||||
if (capacityError != null) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", capacityError));
|
||||
}
|
||||
@@ -112,7 +119,11 @@ public class SubgroupController {
|
||||
|
||||
@DeleteMapping("/api/groups/{groupId}/subgroups/{id}")
|
||||
@RequireRoles({Role.ADMIN, Role.EDUCATION_OFFICE})
|
||||
@Transactional
|
||||
public ResponseEntity<?> delete(@PathVariable Long groupId, @PathVariable Long id) {
|
||||
if (groupRepository.findByIdForUpdate(groupId).isEmpty()) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
Subgroup subgroup = subgroupRepository.findByIdAndStudentGroupId(id, groupId).orElse(null);
|
||||
if (subgroup == null) {
|
||||
return ResponseEntity.notFound().build();
|
||||
|
||||
@@ -17,7 +17,7 @@ public class Subgroup extends LifecycleEntity {
|
||||
@Column(nullable = false, length = 100)
|
||||
private String name;
|
||||
|
||||
@Column(name = "student_capacity")
|
||||
@Column(name = "student_capacity", nullable = false)
|
||||
private Integer studentCapacity;
|
||||
|
||||
public Long getId() {
|
||||
|
||||
@@ -19,6 +19,7 @@ import com.magistr.app.repository.GroupRepository;
|
||||
import com.magistr.app.repository.SpecialtiesRepository;
|
||||
import com.magistr.app.repository.SpecialtyProfileRepository;
|
||||
import com.magistr.app.repository.StudentGroupCalendarAssignmentRepository;
|
||||
import com.magistr.app.repository.SubgroupRepository;
|
||||
import com.magistr.app.utils.CourseAndSemesterCalculator;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -41,6 +42,7 @@ public class AcademicStructureService {
|
||||
private final EducationFormRepository educationFormRepository;
|
||||
private final GroupRepository groupRepository;
|
||||
private final StudentGroupCalendarAssignmentRepository assignmentRepository;
|
||||
private final SubgroupRepository subgroupRepository;
|
||||
private final ScheduleGeneratorService scheduleGeneratorService;
|
||||
|
||||
public AcademicStructureService(AcademicCalendarRepository calendarRepository,
|
||||
@@ -52,6 +54,7 @@ public class AcademicStructureService {
|
||||
EducationFormRepository educationFormRepository,
|
||||
GroupRepository groupRepository,
|
||||
StudentGroupCalendarAssignmentRepository assignmentRepository,
|
||||
SubgroupRepository subgroupRepository,
|
||||
ScheduleGeneratorService scheduleGeneratorService) {
|
||||
this.calendarRepository = calendarRepository;
|
||||
this.calendarDayRepository = calendarDayRepository;
|
||||
@@ -62,9 +65,22 @@ public class AcademicStructureService {
|
||||
this.educationFormRepository = educationFormRepository;
|
||||
this.groupRepository = groupRepository;
|
||||
this.assignmentRepository = assignmentRepository;
|
||||
this.subgroupRepository = subgroupRepository;
|
||||
this.scheduleGeneratorService = scheduleGeneratorService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public StudentGroup createGroup(CreateGroupRequest request) {
|
||||
validateGroupRequest(request);
|
||||
GroupDimensions candidate = resolveGroupDimensions(request);
|
||||
|
||||
StudentGroup group = new StudentGroup();
|
||||
applyGroupDimensions(group, request, candidate);
|
||||
StudentGroup saved = saveGroup(group);
|
||||
clearScheduleCacheAfterCommit();
|
||||
return saved;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public AcademicCalendar updateCalendar(Long id, AcademicCalendarDto request) {
|
||||
validateCalendarRequest(request);
|
||||
@@ -122,6 +138,13 @@ public class AcademicStructureService {
|
||||
validateGroupRequest(request);
|
||||
StudentGroup group = groupRepository.findByIdForUpdate(id)
|
||||
.orElseThrow(() -> new NoSuchElementException("Учебная группа не найдена"));
|
||||
long activeSubgroupCapacity = subgroupRepository.sumActiveStudentCapacityByGroupId(id);
|
||||
if (request.getGroupSize() < activeSubgroupCapacity) {
|
||||
throw new IllegalArgumentException(
|
||||
"Численность группы не может быть меньше суммы численностей активных подгрупп ("
|
||||
+ activeSubgroupCapacity + " чел.)"
|
||||
);
|
||||
}
|
||||
GroupDimensions candidate = resolveGroupDimensions(request);
|
||||
|
||||
boolean incompatibleAssignment = assignmentRepository.findByGroupIdWithDetails(id).stream()
|
||||
@@ -139,13 +162,7 @@ public class AcademicStructureService {
|
||||
);
|
||||
}
|
||||
|
||||
group.setName(request.getName().trim());
|
||||
group.setGroupSize(request.getGroupSize());
|
||||
group.setEducationForm(candidate.studyForm());
|
||||
group.setDepartmentId(request.getDepartmentId());
|
||||
group.setYearStartStudy(request.getYearStartStudy());
|
||||
group.setSpeciality(candidate.speciality());
|
||||
group.setSpecialtyProfile(candidate.profile());
|
||||
applyGroupDimensions(group, request, candidate);
|
||||
StudentGroup saved = saveGroup(group);
|
||||
clearScheduleCacheAfterCommit();
|
||||
return saved;
|
||||
@@ -217,26 +234,38 @@ public class AcademicStructureService {
|
||||
if (request.getName() == null || request.getName().isBlank()) {
|
||||
throw new IllegalArgumentException("Название группы обязательно");
|
||||
}
|
||||
if (request.getGroupSize() == null) {
|
||||
throw new IllegalArgumentException("Численность группы обязательна");
|
||||
if (request.getGroupSize() == null || request.getGroupSize() <= 0) {
|
||||
throw new IllegalArgumentException("Численность группы должна быть больше нуля");
|
||||
}
|
||||
if (request.getEducationFormId() == null) {
|
||||
if (request.getEducationFormId() == null || request.getEducationFormId() <= 0) {
|
||||
throw new IllegalArgumentException("Форма обучения обязательна");
|
||||
}
|
||||
if (request.getDepartmentId() == null || request.getDepartmentId() == 0) {
|
||||
if (request.getDepartmentId() == null || request.getDepartmentId() <= 0) {
|
||||
throw new IllegalArgumentException("ID кафедры обязателен");
|
||||
}
|
||||
if (request.getYearStartStudy() == null || request.getYearStartStudy() == 0) {
|
||||
throw new IllegalArgumentException("Год начала обучения обязателен");
|
||||
if (request.getYearStartStudy() == null || request.getYearStartStudy() <= 0) {
|
||||
throw new IllegalArgumentException("Год начала обучения должен быть больше нуля");
|
||||
}
|
||||
if (request.getEffectiveSpecialtyId() == null || request.getEffectiveSpecialtyId() == 0) {
|
||||
if (request.getEffectiveSpecialtyId() == null || request.getEffectiveSpecialtyId() <= 0) {
|
||||
throw new IllegalArgumentException("Код специальности обязателен");
|
||||
}
|
||||
if (request.getSpecialtyProfileId() == null || request.getSpecialtyProfileId() == 0) {
|
||||
if (request.getSpecialtyProfileId() == null || request.getSpecialtyProfileId() <= 0) {
|
||||
throw new IllegalArgumentException("Профиль обучения обязателен");
|
||||
}
|
||||
}
|
||||
|
||||
private void applyGroupDimensions(StudentGroup group,
|
||||
CreateGroupRequest request,
|
||||
GroupDimensions candidate) {
|
||||
group.setName(request.getName().trim());
|
||||
group.setGroupSize(request.getGroupSize());
|
||||
group.setEducationForm(candidate.studyForm());
|
||||
group.setDepartmentId(request.getDepartmentId());
|
||||
group.setYearStartStudy(request.getYearStartStudy());
|
||||
group.setSpeciality(candidate.speciality());
|
||||
group.setSpecialtyProfile(candidate.profile());
|
||||
}
|
||||
|
||||
private CalendarDimensions resolveCalendarDimensions(AcademicCalendarDto request) {
|
||||
AcademicYear academicYear = academicYearRepository.findById(request.academicYearId())
|
||||
.orElseThrow(() -> new IllegalArgumentException("Учебный год не найден"));
|
||||
|
||||
@@ -284,7 +284,8 @@ public class ScheduleGeneratorService {
|
||||
if (endDate.isBefore(startDate)) {
|
||||
throw new IllegalArgumentException("Дата окончания не может быть раньше даты начала");
|
||||
}
|
||||
if (java.time.temporal.ChronoUnit.DAYS.between(startDate, endDate) > MAX_RANGE_DAYS) {
|
||||
long inclusiveDays = java.time.temporal.ChronoUnit.DAYS.between(startDate, endDate) + 1;
|
||||
if (inclusiveDays > MAX_RANGE_DAYS) {
|
||||
throw new IllegalArgumentException("Диапазон расписания не может превышать 120 дней");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,7 +298,8 @@ public class ScheduleQueryService {
|
||||
if (endDate.isBefore(startDate)) {
|
||||
throw new IllegalArgumentException("Дата окончания не может быть раньше даты начала");
|
||||
}
|
||||
if (ChronoUnit.DAYS.between(startDate, endDate) > MAX_RANGE_DAYS) {
|
||||
long inclusiveDays = ChronoUnit.DAYS.between(startDate, endDate) + 1;
|
||||
if (inclusiveDays > MAX_RANGE_DAYS) {
|
||||
throw new IllegalArgumentException("Диапазон расписания не может превышать 120 дней");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user