merge: объединить изменения команды в Redesign
This commit is contained in:
@@ -7,7 +7,6 @@ import com.magistr.app.repository.*;
|
||||
import com.magistr.app.service.AcademicCalendarGridService;
|
||||
import com.magistr.app.service.AcademicStructureService;
|
||||
import com.magistr.app.service.ScheduleGeneratorService;
|
||||
import com.magistr.app.utils.AcademicCalendarTitleBuilder;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@@ -28,10 +27,6 @@ public class AcademicCalendarController {
|
||||
|
||||
private final AcademicCalendarRepository calendarRepository;
|
||||
private final AcademicCalendarPeriodRepository calendarPeriodRepository;
|
||||
private final AcademicYearRepository academicYearRepository;
|
||||
private final SpecialtiesRepository specialtiesRepository;
|
||||
private final SpecialtyProfileRepository profileRepository;
|
||||
private final EducationFormRepository educationFormRepository;
|
||||
private final SubjectRepository subjectRepository;
|
||||
private final AcademicCalendarSubjectRepository calendarSubjectRepository;
|
||||
private final AcademicCalendarGridService calendarGridService;
|
||||
@@ -40,10 +35,6 @@ public class AcademicCalendarController {
|
||||
|
||||
public AcademicCalendarController(AcademicCalendarRepository calendarRepository,
|
||||
AcademicCalendarPeriodRepository calendarPeriodRepository,
|
||||
AcademicYearRepository academicYearRepository,
|
||||
SpecialtiesRepository specialtiesRepository,
|
||||
SpecialtyProfileRepository profileRepository,
|
||||
EducationFormRepository educationFormRepository,
|
||||
SubjectRepository subjectRepository,
|
||||
AcademicCalendarSubjectRepository calendarSubjectRepository,
|
||||
AcademicCalendarGridService calendarGridService,
|
||||
@@ -51,10 +42,6 @@ public class AcademicCalendarController {
|
||||
AcademicStructureService academicStructureService) {
|
||||
this.calendarRepository = calendarRepository;
|
||||
this.calendarPeriodRepository = calendarPeriodRepository;
|
||||
this.academicYearRepository = academicYearRepository;
|
||||
this.specialtiesRepository = specialtiesRepository;
|
||||
this.profileRepository = profileRepository;
|
||||
this.educationFormRepository = educationFormRepository;
|
||||
this.subjectRepository = subjectRepository;
|
||||
this.calendarSubjectRepository = calendarSubjectRepository;
|
||||
this.calendarGridService = calendarGridService;
|
||||
@@ -81,10 +68,7 @@ public class AcademicCalendarController {
|
||||
@PostMapping
|
||||
public ResponseEntity<?> createCalendar(@RequestBody AcademicCalendarDto request) {
|
||||
try {
|
||||
AcademicCalendar calendar = new AcademicCalendar();
|
||||
applyCalendar(calendar, request);
|
||||
scheduleGeneratorService.clearCache();
|
||||
return ResponseEntity.ok(toCalendarDto(calendarRepository.save(calendar)));
|
||||
return ResponseEntity.ok(toCalendarDto(academicStructureService.createCalendar(request)));
|
||||
} catch (IllegalArgumentException e) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", e.getMessage()));
|
||||
}
|
||||
@@ -153,41 +137,6 @@ public class AcademicCalendarController {
|
||||
}
|
||||
}
|
||||
|
||||
private void applyCalendar(AcademicCalendar calendar, AcademicCalendarDto request) {
|
||||
if (request == null) {
|
||||
throw new IllegalArgumentException("Передайте данные календарного графика");
|
||||
}
|
||||
if (request.courseCount() == null || request.courseCount() <= 0) {
|
||||
throw new IllegalArgumentException("Количество курсов должно быть больше нуля");
|
||||
}
|
||||
if (request.courseCount() > 8) {
|
||||
throw new IllegalArgumentException("Количество курсов не может быть больше 8");
|
||||
}
|
||||
if (request.academicYearId() == null || request.specialtyId() == null
|
||||
|| request.specialtyProfileId() == null || request.studyFormId() == null) {
|
||||
throw new IllegalArgumentException("Учебный год, специальность, профиль и форма обучения обязательны");
|
||||
}
|
||||
|
||||
AcademicYear year = academicYearRepository.findById(request.academicYearId())
|
||||
.orElseThrow(() -> new IllegalArgumentException("Учебный год не найден"));
|
||||
Speciality speciality = specialtiesRepository.findById(request.specialtyId())
|
||||
.orElseThrow(() -> new IllegalArgumentException("Специальность не найдена"));
|
||||
SpecialtyProfile profile = profileRepository.findById(request.specialtyProfileId())
|
||||
.orElseThrow(() -> new IllegalArgumentException("Профиль обучения не найден"));
|
||||
if (!profile.getSpeciality().getId().equals(speciality.getId())) {
|
||||
throw new IllegalArgumentException("Профиль не относится к выбранной специальности");
|
||||
}
|
||||
EducationForm studyForm = educationFormRepository.findById(request.studyFormId())
|
||||
.orElseThrow(() -> new IllegalArgumentException("Форма обучения не найдена"));
|
||||
|
||||
calendar.setTitle(AcademicCalendarTitleBuilder.build(year, speciality, profile, studyForm));
|
||||
calendar.setAcademicYear(year);
|
||||
calendar.setSpeciality(speciality);
|
||||
calendar.setSpecialtyProfile(profile);
|
||||
calendar.setStudyForm(studyForm);
|
||||
calendar.setCourseCount(request.courseCount());
|
||||
}
|
||||
|
||||
private void replaceCalendarSubjects(AcademicCalendar calendar, List<AcademicCalendarSubjectDto> rows) {
|
||||
if (rows.size() > 1000) {
|
||||
throw new IllegalArgumentException("Слишком много дисциплин для одного графика");
|
||||
|
||||
@@ -53,7 +53,7 @@ public class WorkloadController {
|
||||
List<RenderedLessonDto> lessons = scheduleQueryService.searchForAggregation(
|
||||
effectiveDepartmentId, null, startDate, endDate
|
||||
);
|
||||
return summarizeTeacherWorkload(lessons, startDate, endDate);
|
||||
return summarizeTeacherWorkload(lessons, startDate, endDate, effectiveDepartmentId);
|
||||
}
|
||||
|
||||
@GetMapping("/classrooms")
|
||||
@@ -98,15 +98,19 @@ public class WorkloadController {
|
||||
|
||||
private List<WorkloadSummaryDto> summarizeTeacherWorkload(List<RenderedLessonDto> lessons,
|
||||
LocalDate startDate,
|
||||
LocalDate endDate) {
|
||||
Map<Long, List<TeacherDepartmentAssignment>> assignmentsByTeacher =
|
||||
assignmentsByTeacher(lessons, startDate, endDate);
|
||||
LocalDate endDate,
|
||||
Long scopedDepartmentId) {
|
||||
Map<Long, List<TeacherDepartmentAssignment>> assignmentsByTeacher = scopedDepartmentId == null
|
||||
? assignmentsByTeacher(lessons, startDate, endDate)
|
||||
: Map.of();
|
||||
Map<TeacherDepartmentKey, List<RenderedLessonDto>> grouped = lessons.stream()
|
||||
.filter(lesson -> lesson.teacherId() != null)
|
||||
.collect(Collectors.groupingBy(
|
||||
lesson -> new TeacherDepartmentKey(
|
||||
lesson.teacherId(),
|
||||
departmentIdAtLesson(lesson, assignmentsByTeacher).orElse(null)
|
||||
scopedDepartmentId != null
|
||||
? scopedDepartmentId
|
||||
: departmentIdAtLesson(lesson, assignmentsByTeacher).orElse(null)
|
||||
),
|
||||
LinkedHashMap::new,
|
||||
Collectors.toList()
|
||||
|
||||
@@ -4,12 +4,15 @@ import com.magistr.app.dto.AcademicCalendarDto;
|
||||
import com.magistr.app.dto.CreateGroupRequest;
|
||||
import com.magistr.app.dto.GroupCalendarAssignmentDto;
|
||||
import com.magistr.app.model.AcademicCalendar;
|
||||
import com.magistr.app.model.AcademicCalendarActivityType;
|
||||
import com.magistr.app.model.AcademicCalendarPeriod;
|
||||
import com.magistr.app.model.AcademicYear;
|
||||
import com.magistr.app.model.EducationForm;
|
||||
import com.magistr.app.model.Speciality;
|
||||
import com.magistr.app.model.SpecialtyProfile;
|
||||
import com.magistr.app.model.StudentGroup;
|
||||
import com.magistr.app.model.StudentGroupCalendarAssignment;
|
||||
import com.magistr.app.repository.AcademicCalendarActivityTypeRepository;
|
||||
import com.magistr.app.repository.AcademicCalendarPeriodRepository;
|
||||
import com.magistr.app.repository.AcademicCalendarRepository;
|
||||
import com.magistr.app.repository.AcademicCalendarSubjectRepository;
|
||||
@@ -28,14 +31,18 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.TransactionSynchronization;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
@Service
|
||||
public class AcademicStructureService {
|
||||
|
||||
private static final String THEORY_ACTIVITY_CODE = "Т";
|
||||
|
||||
private final AcademicCalendarRepository calendarRepository;
|
||||
private final AcademicCalendarPeriodRepository calendarPeriodRepository;
|
||||
private final AcademicCalendarActivityTypeRepository activityTypeRepository;
|
||||
private final AcademicCalendarSubjectRepository calendarSubjectRepository;
|
||||
private final AcademicYearRepository academicYearRepository;
|
||||
private final SpecialtiesRepository specialtiesRepository;
|
||||
@@ -48,6 +55,7 @@ public class AcademicStructureService {
|
||||
|
||||
public AcademicStructureService(AcademicCalendarRepository calendarRepository,
|
||||
AcademicCalendarPeriodRepository calendarPeriodRepository,
|
||||
AcademicCalendarActivityTypeRepository activityTypeRepository,
|
||||
AcademicCalendarSubjectRepository calendarSubjectRepository,
|
||||
AcademicYearRepository academicYearRepository,
|
||||
SpecialtiesRepository specialtiesRepository,
|
||||
@@ -59,6 +67,7 @@ public class AcademicStructureService {
|
||||
ScheduleGeneratorService scheduleGeneratorService) {
|
||||
this.calendarRepository = calendarRepository;
|
||||
this.calendarPeriodRepository = calendarPeriodRepository;
|
||||
this.activityTypeRepository = activityTypeRepository;
|
||||
this.calendarSubjectRepository = calendarSubjectRepository;
|
||||
this.academicYearRepository = academicYearRepository;
|
||||
this.specialtiesRepository = specialtiesRepository;
|
||||
@@ -82,6 +91,41 @@ public class AcademicStructureService {
|
||||
return saved;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public AcademicCalendar createCalendar(AcademicCalendarDto request) {
|
||||
validateCalendarRequest(request);
|
||||
CalendarDimensions dimensions = resolveCalendarDimensions(request);
|
||||
AcademicCalendarActivityType theoryActivity = activityTypeRepository.findByCode(THEORY_ACTIVITY_CODE)
|
||||
.filter(activity -> Boolean.TRUE.equals(activity.getAllowSchedule()))
|
||||
.orElseThrow(() -> new IllegalArgumentException(
|
||||
"Код активности «Т», разрешающий обычные занятия, не найден"
|
||||
));
|
||||
|
||||
AcademicCalendar calendar = new AcademicCalendar();
|
||||
calendar.setTitle(AcademicCalendarTitleBuilder.build(
|
||||
dimensions.academicYear(),
|
||||
dimensions.speciality(),
|
||||
dimensions.profile(),
|
||||
dimensions.studyForm()
|
||||
));
|
||||
applyCalendarDimensions(calendar, dimensions, request.courseCount());
|
||||
AcademicCalendar saved = saveCalendar(calendar);
|
||||
|
||||
List<AcademicCalendarPeriod> defaultPeriods = new ArrayList<>(request.courseCount());
|
||||
for (int courseNumber = 1; courseNumber <= request.courseCount(); courseNumber++) {
|
||||
AcademicCalendarPeriod period = new AcademicCalendarPeriod();
|
||||
period.setAcademicCalendar(saved);
|
||||
period.setCourseNumber(courseNumber);
|
||||
period.setStartDate(dimensions.academicYear().getStartDate());
|
||||
period.setEndDate(dimensions.academicYear().getEndDate());
|
||||
period.setActivityType(theoryActivity);
|
||||
defaultPeriods.add(period);
|
||||
}
|
||||
calendarPeriodRepository.saveAll(defaultPeriods);
|
||||
clearScheduleCacheAfterCommit();
|
||||
return saved;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public AcademicCalendar updateCalendar(Long id, AcademicCalendarDto request) {
|
||||
validateCalendarRequest(request);
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.magistr.app.service;
|
||||
import com.magistr.app.dto.RenderedLessonDto;
|
||||
import com.magistr.app.model.ScheduleOverride;
|
||||
import com.magistr.app.model.StudentGroup;
|
||||
import com.magistr.app.model.TeacherDepartmentAssignment;
|
||||
import com.magistr.app.repository.GroupRepository;
|
||||
import com.magistr.app.repository.ScheduleOverrideRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -24,25 +25,38 @@ public class ScheduleQueryService {
|
||||
private final ScheduleOverrideRepository scheduleOverrideRepository;
|
||||
private final StudentGroupLifecycleService groupLifecycleService;
|
||||
private final AcademicDateService academicDateService;
|
||||
private final TeacherDepartmentService teacherDepartmentService;
|
||||
|
||||
@Autowired
|
||||
public ScheduleQueryService(ScheduleGeneratorService scheduleGeneratorService,
|
||||
GroupRepository groupRepository,
|
||||
ScheduleOverrideRepository scheduleOverrideRepository,
|
||||
StudentGroupLifecycleService groupLifecycleService,
|
||||
AcademicDateService academicDateService) {
|
||||
AcademicDateService academicDateService,
|
||||
TeacherDepartmentService teacherDepartmentService) {
|
||||
this.scheduleGeneratorService = scheduleGeneratorService;
|
||||
this.groupRepository = groupRepository;
|
||||
this.scheduleOverrideRepository = scheduleOverrideRepository;
|
||||
this.groupLifecycleService = groupLifecycleService;
|
||||
this.academicDateService = academicDateService;
|
||||
this.teacherDepartmentService = teacherDepartmentService;
|
||||
}
|
||||
|
||||
ScheduleQueryService(ScheduleGeneratorService scheduleGeneratorService,
|
||||
GroupRepository groupRepository,
|
||||
ScheduleOverrideRepository scheduleOverrideRepository,
|
||||
StudentGroupLifecycleService groupLifecycleService,
|
||||
AcademicDateService academicDateService) {
|
||||
this(scheduleGeneratorService, groupRepository, scheduleOverrideRepository,
|
||||
groupLifecycleService, academicDateService, null);
|
||||
}
|
||||
|
||||
ScheduleQueryService(ScheduleGeneratorService scheduleGeneratorService,
|
||||
GroupRepository groupRepository,
|
||||
ScheduleOverrideRepository scheduleOverrideRepository,
|
||||
StudentGroupLifecycleService groupLifecycleService) {
|
||||
this(scheduleGeneratorService, groupRepository, scheduleOverrideRepository, groupLifecycleService, null);
|
||||
this(scheduleGeneratorService, groupRepository, scheduleOverrideRepository,
|
||||
groupLifecycleService, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -192,10 +206,19 @@ public class ScheduleQueryService {
|
||||
.findByLessonDateBetweenWithDetails(startDate, endDate);
|
||||
generatedLessons = new ArrayList<>(generatedLessons);
|
||||
addOverrideSourceOccurrences(generatedLessons, overrideSnapshot, scopedGroups);
|
||||
List<RenderedLessonDto> lessons = applyOverrides(generatedLessons, null, overrideSnapshot).stream()
|
||||
List<RenderedLessonDto> effectiveLessons = applyOverrides(generatedLessons, null, overrideSnapshot);
|
||||
Map<Long, List<TeacherDepartmentAssignment>> assignmentsByTeacher =
|
||||
assignmentsByTeacher(effectiveLessons, departmentId, startDate, endDate);
|
||||
List<RenderedLessonDto> lessons = effectiveLessons.stream()
|
||||
.filter(lesson -> !lesson.date().isBefore(startDate) && !lesson.date().isAfter(endDate))
|
||||
.filter(lesson -> teacherId == null || Objects.equals(lesson.teacherId(), teacherId))
|
||||
.filter(lesson -> classroomId == null || Objects.equals(lesson.classroomId(), classroomId))
|
||||
.filter(lesson -> departmentId == null || teacherDepartmentService
|
||||
.hasDepartmentAssignmentAtDate(
|
||||
assignmentsByTeacher.getOrDefault(lesson.teacherId(), List.of()),
|
||||
departmentId,
|
||||
lesson.date()
|
||||
))
|
||||
.filter(lesson -> subjectId == null || Objects.equals(lesson.subjectId(), subjectId))
|
||||
.filter(lesson -> lessonTypeId == null || Objects.equals(lesson.lessonTypeId(), lessonTypeId))
|
||||
.filter(lesson -> timeSlotId == null || Objects.equals(lesson.timeSlotId(), timeSlotId))
|
||||
@@ -204,6 +227,29 @@ public class ScheduleQueryService {
|
||||
return deduplicate(lessons);
|
||||
}
|
||||
|
||||
private Map<Long, List<TeacherDepartmentAssignment>> assignmentsByTeacher(
|
||||
List<RenderedLessonDto> lessons,
|
||||
Long departmentId,
|
||||
LocalDate startDate,
|
||||
LocalDate endDate
|
||||
) {
|
||||
if (departmentId == null) {
|
||||
return Map.of();
|
||||
}
|
||||
if (teacherDepartmentService == null) {
|
||||
throw new IllegalStateException("Сервис кафедральных связей преподавателей недоступен");
|
||||
}
|
||||
Set<Long> teacherIds = lessons.stream()
|
||||
.map(RenderedLessonDto::teacherId)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
return teacherDepartmentService.findAssignmentsForTeachersBetween(
|
||||
teacherIds,
|
||||
startDate,
|
||||
endDate
|
||||
);
|
||||
}
|
||||
|
||||
private void addOverrideSourceOccurrences(
|
||||
List<RenderedLessonDto> generatedLessons,
|
||||
Collection<ScheduleOverride> overrideSnapshot,
|
||||
@@ -395,12 +441,10 @@ public class ScheduleQueryService {
|
||||
.map(List::of)
|
||||
.orElse(List.of());
|
||||
}
|
||||
List<StudentGroup> groups;
|
||||
if (departmentId != null) {
|
||||
groups = groupRepository.findByDepartmentId(departmentId);
|
||||
} else {
|
||||
groups = groupRepository.findAll();
|
||||
}
|
||||
// Кафедральный scope определяется по преподавателю на дату занятия,
|
||||
// а не по выпускающей кафедре учебной группы. Фильтрация выполняется
|
||||
// после генерации и применения точечных изменений.
|
||||
List<StudentGroup> groups = groupRepository.findAll();
|
||||
groups = groups.stream()
|
||||
.filter(group -> groupLifecycleService.mayHaveScheduleInRange(group, startDate, endDate))
|
||||
.toList();
|
||||
|
||||
@@ -136,6 +136,23 @@ public class TeacherDepartmentService {
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
public boolean hasDepartmentAssignmentAtDate(
|
||||
Collection<TeacherDepartmentAssignment> assignments,
|
||||
Long departmentId,
|
||||
LocalDate date
|
||||
) {
|
||||
if (assignments == null || departmentId == null || date == null) {
|
||||
return false;
|
||||
}
|
||||
return assignments.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.filter(assignment -> isActiveAt(assignment, date))
|
||||
.map(TeacherDepartmentAssignment::getDepartment)
|
||||
.filter(Objects::nonNull)
|
||||
.map(Department::getId)
|
||||
.anyMatch(departmentId::equals);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public TeacherDepartmentAssignment transferTeacher(Long teacherId,
|
||||
DepartmentTransferRequest request,
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
INSERT INTO academic_calendar_periods (
|
||||
calendar_id,
|
||||
course_number,
|
||||
start_date,
|
||||
end_date,
|
||||
activity_type_id
|
||||
)
|
||||
SELECT
|
||||
calendar.id,
|
||||
course.course_number,
|
||||
academic_year.start_date,
|
||||
academic_year.end_date,
|
||||
theory_activity.id
|
||||
FROM academic_calendars calendar
|
||||
JOIN academic_years academic_year
|
||||
ON academic_year.id = calendar.academic_year_id
|
||||
JOIN academic_calendar_activity_types theory_activity
|
||||
ON theory_activity.code = 'Т'
|
||||
CROSS JOIN LATERAL GENERATE_SERIES(1, calendar.course_count) AS course(course_number)
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM academic_calendar_periods existing_period
|
||||
WHERE existing_period.calendar_id = calendar.id
|
||||
);
|
||||
@@ -12,10 +12,6 @@ import com.magistr.app.model.SpecialtyProfile;
|
||||
import com.magistr.app.repository.AcademicCalendarPeriodRepository;
|
||||
import com.magistr.app.repository.AcademicCalendarRepository;
|
||||
import com.magistr.app.repository.AcademicCalendarSubjectRepository;
|
||||
import com.magistr.app.repository.AcademicYearRepository;
|
||||
import com.magistr.app.repository.EducationFormRepository;
|
||||
import com.magistr.app.repository.SpecialtiesRepository;
|
||||
import com.magistr.app.repository.SpecialtyProfileRepository;
|
||||
import com.magistr.app.repository.SubjectRepository;
|
||||
import com.magistr.app.service.AcademicCalendarGridService;
|
||||
import com.magistr.app.service.AcademicStructureService;
|
||||
@@ -24,22 +20,17 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class AcademicCalendarControllerTest {
|
||||
|
||||
@Test
|
||||
void createCalendarBuildsTitleWithoutClientValue() {
|
||||
void createCalendarReturnsCalendarCreatedByTransactionalService() {
|
||||
AcademicCalendarRepository calendarRepository = mock(AcademicCalendarRepository.class);
|
||||
AcademicYearRepository academicYearRepository = mock(AcademicYearRepository.class);
|
||||
SpecialtiesRepository specialtiesRepository = mock(SpecialtiesRepository.class);
|
||||
SpecialtyProfileRepository profileRepository = mock(SpecialtyProfileRepository.class);
|
||||
EducationFormRepository educationFormRepository = mock(EducationFormRepository.class);
|
||||
AcademicStructureService academicStructureService = mock(AcademicStructureService.class);
|
||||
|
||||
AcademicYear year = new AcademicYear();
|
||||
year.setId(1L);
|
||||
@@ -55,26 +46,6 @@ class AcademicCalendarControllerTest {
|
||||
studyForm.setId(4L);
|
||||
studyForm.setName("Очная");
|
||||
|
||||
when(academicYearRepository.findById(1L)).thenReturn(Optional.of(year));
|
||||
when(specialtiesRepository.findById(2L)).thenReturn(Optional.of(speciality));
|
||||
when(profileRepository.findById(3L)).thenReturn(Optional.of(profile));
|
||||
when(educationFormRepository.findById(4L)).thenReturn(Optional.of(studyForm));
|
||||
when(calendarRepository.save(any(AcademicCalendar.class)))
|
||||
.thenAnswer(invocation -> invocation.getArgument(0));
|
||||
|
||||
AcademicCalendarController controller = new AcademicCalendarController(
|
||||
calendarRepository,
|
||||
mock(AcademicCalendarPeriodRepository.class),
|
||||
academicYearRepository,
|
||||
specialtiesRepository,
|
||||
profileRepository,
|
||||
educationFormRepository,
|
||||
mock(SubjectRepository.class),
|
||||
mock(AcademicCalendarSubjectRepository.class),
|
||||
mock(AcademicCalendarGridService.class),
|
||||
mock(ScheduleGeneratorService.class),
|
||||
mock(AcademicStructureService.class)
|
||||
);
|
||||
AcademicCalendarDto request = new AcademicCalendarDto(
|
||||
null,
|
||||
null,
|
||||
@@ -91,13 +62,31 @@ class AcademicCalendarControllerTest {
|
||||
null,
|
||||
4
|
||||
);
|
||||
AcademicCalendar saved = new AcademicCalendar();
|
||||
saved.setTitle("09.03.04 — Разработка ПО — Очная — 2026-2027");
|
||||
saved.setAcademicYear(year);
|
||||
saved.setSpeciality(speciality);
|
||||
saved.setSpecialtyProfile(profile);
|
||||
saved.setStudyForm(studyForm);
|
||||
saved.setCourseCount(4);
|
||||
when(academicStructureService.createCalendar(request)).thenReturn(saved);
|
||||
|
||||
AcademicCalendarController controller = new AcademicCalendarController(
|
||||
calendarRepository,
|
||||
mock(AcademicCalendarPeriodRepository.class),
|
||||
mock(SubjectRepository.class),
|
||||
mock(AcademicCalendarSubjectRepository.class),
|
||||
mock(AcademicCalendarGridService.class),
|
||||
mock(ScheduleGeneratorService.class),
|
||||
academicStructureService
|
||||
);
|
||||
|
||||
var response = controller.createCalendar(request);
|
||||
|
||||
assertThat(response.getStatusCode().value()).isEqualTo(200);
|
||||
assertThat(response.getBody()).isInstanceOf(AcademicCalendarDto.class);
|
||||
assertThat(((AcademicCalendarDto) response.getBody()).title())
|
||||
.isEqualTo("09.03.04 — Разработка ПО — Очная — 2026-2027");
|
||||
.isEqualTo(saved.getTitle());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -134,10 +123,6 @@ class AcademicCalendarControllerTest {
|
||||
AcademicCalendarController controller = new AcademicCalendarController(
|
||||
calendarRepository,
|
||||
periodRepository,
|
||||
mock(AcademicYearRepository.class),
|
||||
mock(SpecialtiesRepository.class),
|
||||
mock(SpecialtyProfileRepository.class),
|
||||
mock(EducationFormRepository.class),
|
||||
mock(SubjectRepository.class),
|
||||
mock(AcademicCalendarSubjectRepository.class),
|
||||
mock(AcademicCalendarGridService.class),
|
||||
|
||||
@@ -82,6 +82,36 @@ class WorkloadControllerTest {
|
||||
.containsOnly(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void departmentTeacherWorkloadUsesRequestedAdditionalDepartment() {
|
||||
ScheduleQueryService scheduleQueryService = mock(ScheduleQueryService.class);
|
||||
TeacherDepartmentService teacherDepartmentService = mock(TeacherDepartmentService.class);
|
||||
DepartmentRepository departmentRepository = mock(DepartmentRepository.class);
|
||||
LocalDate startDate = LocalDate.of(2026, 5, 1);
|
||||
LocalDate endDate = LocalDate.of(2026, 5, 31);
|
||||
RenderedLessonDto lesson = lesson(10L, "Петров Пётр", LocalDate.of(2026, 5, 15), 1L);
|
||||
when(scheduleQueryService.searchForAggregation(7L, null, startDate, endDate))
|
||||
.thenReturn(List.of(lesson));
|
||||
when(departmentRepository.findById(7L))
|
||||
.thenReturn(Optional.of(new Department(7L, "Кафедра ИБ", 7L)));
|
||||
WorkloadController controller = new WorkloadController(
|
||||
scheduleQueryService,
|
||||
mock(ClassroomRepository.class),
|
||||
departmentRepository,
|
||||
teacherDepartmentService
|
||||
);
|
||||
authenticate(Role.DEPARTMENT, 7L);
|
||||
|
||||
List<WorkloadSummaryDto> result = controller.teacherWorkload(startDate, endDate, null);
|
||||
|
||||
assertThat(result).singleElement().satisfies(item -> {
|
||||
assertThat(item.id()).isEqualTo(10L);
|
||||
assertThat(item.departmentId()).isEqualTo(7L);
|
||||
assertThat(item.lessonCount()).isEqualTo(1L);
|
||||
});
|
||||
verifyNoInteractions(teacherDepartmentService);
|
||||
}
|
||||
|
||||
@Test
|
||||
void freeClassroomsSkipsNullAvailabilityWithoutNpe() {
|
||||
ScheduleQueryService scheduleQueryService = mock(ScheduleQueryService.class);
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
package com.magistr.app.migration;
|
||||
|
||||
import org.flywaydb.core.Flyway;
|
||||
import org.flywaydb.core.api.MigrationVersion;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.testcontainers.containers.PostgreSQLContainer;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@Testcontainers
|
||||
@DisplayName("Миграция начальных периодов календарного графика")
|
||||
class AcademicCalendarDefaultTheoryPeriodMigrationIntegrationTest {
|
||||
|
||||
@Container
|
||||
static final PostgreSQLContainer<?> POSTGRES =
|
||||
new PostgreSQLContainer<>(com.magistr.app.testing.TestContainerImages.POSTGRES);
|
||||
|
||||
@Test
|
||||
@DisplayName("V2 заполняет кодом Т только полностью пустые графики")
|
||||
void migrationBackfillsOnlyEmptyCalendars() throws SQLException {
|
||||
Flyway baseline = flyway("1");
|
||||
baseline.clean();
|
||||
baseline.migrate();
|
||||
|
||||
long emptyCalendarId;
|
||||
long configuredCalendarId;
|
||||
try (Connection connection = POSTGRES.createConnection("")) {
|
||||
emptyCalendarId = insertCalendarFromSeed(connection, "Пустой график для V2", 3);
|
||||
configuredCalendarId = insertCalendarFromSeed(connection, "Настроенный график для V2", 2);
|
||||
insertConfiguredPeriod(connection, configuredCalendarId);
|
||||
}
|
||||
|
||||
Flyway allMigrations = flyway(null);
|
||||
allMigrations.migrate();
|
||||
|
||||
assertThat(allMigrations.info().current()).isNotNull();
|
||||
assertThat(allMigrations.info().current().getVersion().getVersion()).isEqualTo("2");
|
||||
|
||||
try (Connection connection = POSTGRES.createConnection("")) {
|
||||
assertThat(readSummary(connection, emptyCalendarId))
|
||||
.isEqualTo(new PeriodSummary(3, 1, 3, true));
|
||||
assertThat(readSummary(connection, configuredCalendarId))
|
||||
.isEqualTo(new PeriodSummary(1, 1, 1, false));
|
||||
}
|
||||
}
|
||||
|
||||
private Flyway flyway(String targetVersion) {
|
||||
var configuration = Flyway.configure()
|
||||
.dataSource(POSTGRES.getJdbcUrl(), POSTGRES.getUsername(), POSTGRES.getPassword())
|
||||
.locations("classpath:db/migration")
|
||||
.cleanDisabled(false);
|
||||
if (targetVersion != null) {
|
||||
configuration.target(MigrationVersion.fromVersion(targetVersion));
|
||||
}
|
||||
return configuration.load();
|
||||
}
|
||||
|
||||
private long insertCalendarFromSeed(Connection connection,
|
||||
String title,
|
||||
int courseCount) throws SQLException {
|
||||
try (PreparedStatement statement = connection.prepareStatement("""
|
||||
INSERT INTO academic_calendars (
|
||||
title,
|
||||
academic_year_id,
|
||||
specialty_id,
|
||||
specialty_profile_id,
|
||||
study_form_id,
|
||||
course_count
|
||||
)
|
||||
SELECT ?, academic_year_id, specialty_id, specialty_profile_id, study_form_id, ?
|
||||
FROM academic_calendars
|
||||
ORDER BY id
|
||||
LIMIT 1
|
||||
RETURNING id
|
||||
""")) {
|
||||
statement.setString(1, title);
|
||||
statement.setInt(2, courseCount);
|
||||
try (ResultSet resultSet = statement.executeQuery()) {
|
||||
assertThat(resultSet.next()).isTrue();
|
||||
return resultSet.getLong(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void insertConfiguredPeriod(Connection connection, long calendarId) throws SQLException {
|
||||
try (PreparedStatement statement = connection.prepareStatement("""
|
||||
INSERT INTO academic_calendar_periods (
|
||||
calendar_id,
|
||||
course_number,
|
||||
start_date,
|
||||
end_date,
|
||||
activity_type_id
|
||||
)
|
||||
SELECT calendar.id, 1, academic_year.start_date, academic_year.start_date, activity.id
|
||||
FROM academic_calendars calendar
|
||||
JOIN academic_years academic_year ON academic_year.id = calendar.academic_year_id
|
||||
JOIN academic_calendar_activity_types activity ON activity.code = 'К'
|
||||
WHERE calendar.id = ?
|
||||
""")) {
|
||||
statement.setLong(1, calendarId);
|
||||
assertThat(statement.executeUpdate()).isEqualTo(1);
|
||||
}
|
||||
}
|
||||
|
||||
private PeriodSummary readSummary(Connection connection, long calendarId) throws SQLException {
|
||||
try (PreparedStatement statement = connection.prepareStatement("""
|
||||
SELECT
|
||||
COUNT(*),
|
||||
MIN(period.course_number),
|
||||
MAX(period.course_number),
|
||||
BOOL_AND(
|
||||
activity.code = 'Т'
|
||||
AND period.start_date = academic_year.start_date
|
||||
AND period.end_date = academic_year.end_date
|
||||
)
|
||||
FROM academic_calendar_periods period
|
||||
JOIN academic_calendars calendar ON calendar.id = period.calendar_id
|
||||
JOIN academic_years academic_year ON academic_year.id = calendar.academic_year_id
|
||||
JOIN academic_calendar_activity_types activity ON activity.id = period.activity_type_id
|
||||
WHERE period.calendar_id = ?
|
||||
""")) {
|
||||
statement.setLong(1, calendarId);
|
||||
try (ResultSet resultSet = statement.executeQuery()) {
|
||||
assertThat(resultSet.next()).isTrue();
|
||||
return new PeriodSummary(
|
||||
resultSet.getInt(1),
|
||||
resultSet.getInt(2),
|
||||
resultSet.getInt(3),
|
||||
resultSet.getBoolean(4)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private record PeriodSummary(int count,
|
||||
int minCourse,
|
||||
int maxCourse,
|
||||
boolean fullYearTheory) {
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.magistr.app.migration;
|
||||
|
||||
import org.flywaydb.core.Flyway;
|
||||
import org.flywaydb.core.api.MigrationVersion;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.testcontainers.containers.PostgreSQLContainer;
|
||||
@@ -30,6 +31,7 @@ class AcademicCalendarWeekNumberBaselineIntegrationTest {
|
||||
Flyway flyway = Flyway.configure()
|
||||
.dataSource(POSTGRES.getJdbcUrl(), POSTGRES.getUsername(), POSTGRES.getPassword())
|
||||
.locations("classpath:db/migration")
|
||||
.target(MigrationVersion.fromVersion("1"))
|
||||
.cleanDisabled(false)
|
||||
.load();
|
||||
|
||||
|
||||
@@ -4,12 +4,15 @@ import com.magistr.app.dto.AcademicCalendarDto;
|
||||
import com.magistr.app.dto.CreateGroupRequest;
|
||||
import com.magistr.app.dto.GroupCalendarAssignmentDto;
|
||||
import com.magistr.app.model.AcademicCalendar;
|
||||
import com.magistr.app.model.AcademicCalendarActivityType;
|
||||
import com.magistr.app.model.AcademicCalendarPeriod;
|
||||
import com.magistr.app.model.AcademicYear;
|
||||
import com.magistr.app.model.EducationForm;
|
||||
import com.magistr.app.model.Speciality;
|
||||
import com.magistr.app.model.SpecialtyProfile;
|
||||
import com.magistr.app.model.StudentGroup;
|
||||
import com.magistr.app.model.StudentGroupCalendarAssignment;
|
||||
import com.magistr.app.repository.AcademicCalendarActivityTypeRepository;
|
||||
import com.magistr.app.repository.AcademicCalendarPeriodRepository;
|
||||
import com.magistr.app.repository.AcademicCalendarRepository;
|
||||
import com.magistr.app.repository.AcademicCalendarSubjectRepository;
|
||||
@@ -23,6 +26,7 @@ import com.magistr.app.repository.SubgroupRepository;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
@@ -56,6 +60,8 @@ class AcademicStructureServiceTest {
|
||||
@Mock
|
||||
private AcademicCalendarPeriodRepository calendarPeriodRepository;
|
||||
@Mock
|
||||
private AcademicCalendarActivityTypeRepository activityTypeRepository;
|
||||
@Mock
|
||||
private AcademicCalendarSubjectRepository calendarSubjectRepository;
|
||||
@Mock
|
||||
private AcademicYearRepository academicYearRepository;
|
||||
@@ -88,6 +94,7 @@ class AcademicStructureServiceTest {
|
||||
service = new AcademicStructureService(
|
||||
calendarRepository,
|
||||
calendarPeriodRepository,
|
||||
activityTypeRepository,
|
||||
calendarSubjectRepository,
|
||||
academicYearRepository,
|
||||
specialtiesRepository,
|
||||
@@ -118,12 +125,54 @@ class AcademicStructureServiceTest {
|
||||
when(subgroupRepository.sumActiveStudentCapacityByGroupId(GROUP_ID)).thenReturn(0L);
|
||||
when(calendarRepository.saveAndFlush(any(AcademicCalendar.class)))
|
||||
.thenAnswer(invocation -> invocation.getArgument(0));
|
||||
AcademicCalendarActivityType theoryActivity = new AcademicCalendarActivityType();
|
||||
theoryActivity.setId(80L);
|
||||
theoryActivity.setCode("Т");
|
||||
theoryActivity.setName("Теоретическое обучение");
|
||||
theoryActivity.setAllowSchedule(true);
|
||||
when(activityTypeRepository.findByCode("Т")).thenReturn(Optional.of(theoryActivity));
|
||||
when(groupRepository.saveAndFlush(any(StudentGroup.class)))
|
||||
.thenAnswer(invocation -> invocation.getArgument(0));
|
||||
when(assignmentRepository.saveAndFlush(any(StudentGroupCalendarAssignment.class)))
|
||||
.thenAnswer(invocation -> invocation.getArgument(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createsCalendarWithTheoryPeriodForEveryCourseAcrossWholeAcademicYear() {
|
||||
AcademicCalendar saved = service.createCalendar(calendarRequest(3, FORM_ID));
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ArgumentCaptor<List<AcademicCalendarPeriod>> periodsCaptor = ArgumentCaptor.forClass(List.class);
|
||||
verify(calendarPeriodRepository).saveAll(periodsCaptor.capture());
|
||||
|
||||
assertThat(saved.getTitle())
|
||||
.isEqualTo("09.03.01 — Без профиля — Бакалавриат — 2025-2026");
|
||||
assertThat(periodsCaptor.getValue())
|
||||
.extracting(AcademicCalendarPeriod::getCourseNumber)
|
||||
.containsExactly(1, 2, 3);
|
||||
assertThat(periodsCaptor.getValue()).allSatisfy(period -> {
|
||||
assertThat(period.getAcademicCalendar()).isSameAs(saved);
|
||||
assertThat(period.getStartDate()).isEqualTo(academicYear.getStartDate());
|
||||
assertThat(period.getEndDate()).isEqualTo(academicYear.getEndDate());
|
||||
assertThat(period.getActivityType().getCode()).isEqualTo("Т");
|
||||
assertThat(period.getActivityType().getAllowSchedule()).isTrue();
|
||||
});
|
||||
verify(scheduleGeneratorService).clearCache();
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsCalendarCreationWhenSchedulingTheoryActivityIsMissing() {
|
||||
when(activityTypeRepository.findByCode("Т")).thenReturn(Optional.empty());
|
||||
|
||||
assertThatThrownBy(() -> service.createCalendar(calendarRequest(4, FORM_ID)))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("Код активности «Т», разрешающий обычные занятия, не найден");
|
||||
|
||||
verify(calendarRepository, never()).saveAndFlush(any());
|
||||
verify(calendarPeriodRepository, never()).saveAll(any());
|
||||
verify(scheduleGeneratorService, never()).clearCache();
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsCalendarDimensionChangeAndKeepsEntityUnchanged() {
|
||||
when(assignmentRepository.findByCalendarIdWithDetails(CALENDAR_ID))
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.magistr.app.service;
|
||||
|
||||
import com.magistr.app.dto.RenderedLessonDto;
|
||||
import com.magistr.app.model.TeacherDepartmentAssignment;
|
||||
import com.magistr.app.model.StudentGroup;
|
||||
import com.magistr.app.repository.GroupRepository;
|
||||
import com.magistr.app.repository.ScheduleOverrideRepository;
|
||||
@@ -9,6 +11,8 @@ import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -138,6 +142,63 @@ class ScheduleQueryServiceTest {
|
||||
verify(overrideRepository).findByLessonDateBetweenWithDetails(startDate, endDate);
|
||||
}
|
||||
|
||||
@Test
|
||||
void departmentSearchIncludesTeacherLessonsForGroupsFromAnotherDepartment() {
|
||||
ScheduleGeneratorService generatorService = mock(ScheduleGeneratorService.class);
|
||||
GroupRepository groupRepository = mock(GroupRepository.class);
|
||||
ScheduleOverrideRepository overrideRepository = mock(ScheduleOverrideRepository.class);
|
||||
StudentGroupLifecycleService groupLifecycleService = mock(StudentGroupLifecycleService.class);
|
||||
TeacherDepartmentService teacherDepartmentService = mock(TeacherDepartmentService.class);
|
||||
LocalDate startDate = LocalDate.of(2026, 5, 1);
|
||||
LocalDate endDate = LocalDate.of(2026, 5, 7);
|
||||
LocalDate lessonDate = LocalDate.of(2026, 5, 4);
|
||||
StudentGroup externalGroup = group(20);
|
||||
RenderedLessonDto ownTeacherLesson = mock(RenderedLessonDto.class);
|
||||
RenderedLessonDto anotherDepartmentLesson = mock(RenderedLessonDto.class);
|
||||
TeacherDepartmentAssignment ownAssignment = new TeacherDepartmentAssignment();
|
||||
TeacherDepartmentAssignment anotherAssignment = new TeacherDepartmentAssignment();
|
||||
|
||||
when(ownTeacherLesson.teacherId()).thenReturn(10L);
|
||||
when(ownTeacherLesson.date()).thenReturn(lessonDate);
|
||||
when(anotherDepartmentLesson.teacherId()).thenReturn(11L);
|
||||
when(anotherDepartmentLesson.date()).thenReturn(lessonDate);
|
||||
when(groupRepository.findAll()).thenReturn(List.of(externalGroup));
|
||||
when(groupLifecycleService.mayHaveScheduleInRange(externalGroup, startDate, endDate)).thenReturn(true);
|
||||
when(generatorService.buildScheduleForGroups(any(), eq(startDate), eq(endDate)))
|
||||
.thenReturn(List.of(ownTeacherLesson, anotherDepartmentLesson));
|
||||
when(overrideRepository.findByLessonDateBetweenWithDetails(startDate, endDate)).thenReturn(List.of());
|
||||
when(teacherDepartmentService.findAssignmentsForTeachersBetween(
|
||||
Set.of(10L, 11L),
|
||||
startDate,
|
||||
endDate
|
||||
)).thenReturn(Map.of(
|
||||
10L, List.of(ownAssignment),
|
||||
11L, List.of(anotherAssignment)
|
||||
));
|
||||
when(teacherDepartmentService.hasDepartmentAssignmentAtDate(
|
||||
List.of(ownAssignment), 7L, lessonDate
|
||||
)).thenReturn(true);
|
||||
when(teacherDepartmentService.hasDepartmentAssignmentAtDate(
|
||||
List.of(anotherAssignment), 7L, lessonDate
|
||||
)).thenReturn(false);
|
||||
ScheduleQueryService service = new ScheduleQueryService(
|
||||
generatorService,
|
||||
groupRepository,
|
||||
overrideRepository,
|
||||
groupLifecycleService,
|
||||
mock(AcademicDateService.class),
|
||||
teacherDepartmentService
|
||||
);
|
||||
|
||||
var result = service.searchForAggregation(7L, null, startDate, endDate);
|
||||
|
||||
assertThat(result).singleElement().satisfies(lesson ->
|
||||
assertThat(lesson.teacherId()).isEqualTo(10L)
|
||||
);
|
||||
verify(groupRepository).findAll();
|
||||
verify(groupRepository, never()).findByDepartmentId(7L);
|
||||
}
|
||||
|
||||
private StudentGroup group(int id) {
|
||||
StudentGroup group = new StudentGroup();
|
||||
group.setId((long) id);
|
||||
|
||||
@@ -173,6 +173,28 @@ class TeacherDepartmentServiceTest {
|
||||
)).contains(newDepartment.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void departmentScopeAcceptsAdditionalAssignmentAlongsidePrimary() {
|
||||
TeacherDepartmentAssignment primary = assignment(
|
||||
oldDepartment,
|
||||
LocalDate.of(2025, 9, 1),
|
||||
null,
|
||||
true
|
||||
);
|
||||
TeacherDepartmentAssignment additional = assignment(
|
||||
newDepartment,
|
||||
LocalDate.of(2026, 1, 1),
|
||||
null,
|
||||
false
|
||||
);
|
||||
|
||||
assertThat(service.hasDepartmentAssignmentAtDate(
|
||||
List.of(primary, additional),
|
||||
newDepartment.getId(),
|
||||
TODAY
|
||||
)).isTrue();
|
||||
}
|
||||
|
||||
private void prepareTransfer(TeacherDepartmentAssignment current) {
|
||||
when(assignmentRepository.saveAndFlush(any(TeacherDepartmentAssignment.class)))
|
||||
.thenAnswer(invocation -> invocation.getArgument(0));
|
||||
|
||||
Reference in New Issue
Block a user