251 lines
12 KiB
Java
251 lines
12 KiB
Java
package com.magistr.app.controller;
|
||
|
||
import com.magistr.app.config.auth.RequireRoles;
|
||
import com.magistr.app.dto.*;
|
||
import com.magistr.app.model.*;
|
||
import com.magistr.app.repository.*;
|
||
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;
|
||
import java.util.Map;
|
||
|
||
@RestController
|
||
@RequestMapping("/api/admin/academic-calendars")
|
||
@RequireRoles({Role.ADMIN, Role.EDUCATION_OFFICE})
|
||
public class AcademicCalendarController {
|
||
|
||
private final AcademicCalendarRepository calendarRepository;
|
||
private final AcademicCalendarDayRepository calendarDayRepository;
|
||
private final AcademicCalendarActivityTypeRepository activityTypeRepository;
|
||
private final AcademicYearRepository academicYearRepository;
|
||
private final SpecialtiesRepository specialtiesRepository;
|
||
private final SpecialtyProfileRepository profileRepository;
|
||
private final EducationFormRepository educationFormRepository;
|
||
private final ScheduleGeneratorService scheduleGeneratorService;
|
||
|
||
public AcademicCalendarController(AcademicCalendarRepository calendarRepository,
|
||
AcademicCalendarDayRepository calendarDayRepository,
|
||
AcademicCalendarActivityTypeRepository activityTypeRepository,
|
||
AcademicYearRepository academicYearRepository,
|
||
SpecialtiesRepository specialtiesRepository,
|
||
SpecialtyProfileRepository profileRepository,
|
||
EducationFormRepository educationFormRepository,
|
||
ScheduleGeneratorService scheduleGeneratorService) {
|
||
this.calendarRepository = calendarRepository;
|
||
this.calendarDayRepository = calendarDayRepository;
|
||
this.activityTypeRepository = activityTypeRepository;
|
||
this.academicYearRepository = academicYearRepository;
|
||
this.specialtiesRepository = specialtiesRepository;
|
||
this.profileRepository = profileRepository;
|
||
this.educationFormRepository = educationFormRepository;
|
||
this.scheduleGeneratorService = scheduleGeneratorService;
|
||
}
|
||
|
||
@GetMapping
|
||
public List<AcademicCalendarDto> getCalendars(@RequestParam(required = false) Long academicYearId,
|
||
@RequestParam(required = false) Long specialtyId,
|
||
@RequestParam(required = false) Long profileId) {
|
||
return calendarRepository.findAllWithDetails(academicYearId, specialtyId, profileId).stream()
|
||
.map(this::toCalendarDto)
|
||
.toList();
|
||
}
|
||
|
||
@GetMapping("/{id}")
|
||
public ResponseEntity<?> getCalendar(@PathVariable Long id) {
|
||
return calendarRepository.findByIdWithDetails(id)
|
||
.<ResponseEntity<?>>map(calendar -> ResponseEntity.ok(toCalendarDto(calendar)))
|
||
.orElseGet(() -> ResponseEntity.notFound().build());
|
||
}
|
||
|
||
@PostMapping
|
||
public ResponseEntity<?> createCalendar(@RequestBody AcademicCalendarDto request) {
|
||
try {
|
||
AcademicCalendar calendar = new AcademicCalendar();
|
||
applyCalendar(calendar, request);
|
||
scheduleGeneratorService.clearCache();
|
||
return ResponseEntity.ok(toCalendarDto(calendarRepository.save(calendar)));
|
||
} catch (IllegalArgumentException e) {
|
||
return ResponseEntity.badRequest().body(Map.of("message", e.getMessage()));
|
||
}
|
||
}
|
||
|
||
@PutMapping("/{id}")
|
||
public ResponseEntity<?> updateCalendar(@PathVariable Long id, @RequestBody AcademicCalendarDto request) {
|
||
AcademicCalendar calendar = calendarRepository.findById(id).orElse(null);
|
||
if (calendar == null) {
|
||
return ResponseEntity.notFound().build();
|
||
}
|
||
try {
|
||
applyCalendar(calendar, request);
|
||
scheduleGeneratorService.clearCache();
|
||
return ResponseEntity.ok(toCalendarDto(calendarRepository.save(calendar)));
|
||
} catch (IllegalArgumentException e) {
|
||
return ResponseEntity.badRequest().body(Map.of("message", e.getMessage()));
|
||
}
|
||
}
|
||
|
||
@DeleteMapping("/{id}")
|
||
public ResponseEntity<?> deleteCalendar(@PathVariable Long id) {
|
||
if (!calendarRepository.existsById(id)) {
|
||
return ResponseEntity.notFound().build();
|
||
}
|
||
try {
|
||
calendarRepository.deleteById(id);
|
||
scheduleGeneratorService.clearCache();
|
||
return ResponseEntity.ok(Map.of("message", "Календарный график удалён"));
|
||
} catch (Exception e) {
|
||
return ResponseEntity.badRequest()
|
||
.body(Map.of("message", "Нельзя удалить график, который назначен учебным группам"));
|
||
}
|
||
}
|
||
|
||
@GetMapping("/{id}/grid")
|
||
public ResponseEntity<?> getGrid(@PathVariable Long id) {
|
||
if (!calendarRepository.existsById(id)) {
|
||
return ResponseEntity.notFound().build();
|
||
}
|
||
return ResponseEntity.ok(calendarDayRepository.findByCalendarIdWithActivity(id).stream()
|
||
.map(this::toGridDayDto)
|
||
.toList());
|
||
}
|
||
|
||
@PutMapping("/{id}/grid")
|
||
@Transactional
|
||
public ResponseEntity<?> saveGrid(@PathVariable Long id, @RequestBody List<AcademicCalendarGridDayDto> rows) {
|
||
AcademicCalendar calendar = calendarRepository.findByIdWithDetails(id).orElse(null);
|
||
if (calendar == null) {
|
||
return ResponseEntity.notFound().build();
|
||
}
|
||
if (rows == null || rows.isEmpty()) {
|
||
return ResponseEntity.badRequest().body(Map.of("message", "Передайте хотя бы одну ячейку графика"));
|
||
}
|
||
|
||
try {
|
||
calendarDayRepository.deleteByCalendarId(id);
|
||
for (AcademicCalendarGridDayDto row : rows) {
|
||
AcademicCalendarDay day = buildGridDay(calendar, row);
|
||
calendarDayRepository.save(day);
|
||
}
|
||
scheduleGeneratorService.clearCache();
|
||
return ResponseEntity.ok(Map.of("message", "Календарный учебный график сохранён"));
|
||
} catch (IllegalArgumentException e) {
|
||
return ResponseEntity.badRequest().body(Map.of("message", e.getMessage()));
|
||
}
|
||
}
|
||
|
||
private void applyCalendar(AcademicCalendar calendar, AcademicCalendarDto request) {
|
||
if (request == null) {
|
||
throw new IllegalArgumentException("Передайте данные календарного графика");
|
||
}
|
||
if (request.title() == null || request.title().isBlank()) {
|
||
throw new IllegalArgumentException("Название графика обязательно");
|
||
}
|
||
if (request.courseCount() == null || request.courseCount() <= 0) {
|
||
throw new IllegalArgumentException("Количество курсов должно быть больше нуля");
|
||
}
|
||
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(request.title().trim());
|
||
calendar.setAcademicYear(year);
|
||
calendar.setSpeciality(speciality);
|
||
calendar.setSpecialtyProfile(profile);
|
||
calendar.setStudyForm(studyForm);
|
||
calendar.setCourseCount(request.courseCount());
|
||
}
|
||
|
||
private AcademicCalendarDay buildGridDay(AcademicCalendar calendar, AcademicCalendarGridDayDto row) {
|
||
if (row.courseNumber() == null || row.courseNumber() <= 0
|
||
|| row.date() == null
|
||
|| row.weekNumber() == null || row.weekNumber() <= 0
|
||
|| row.dayOfWeek() == null || row.dayOfWeek() < 1 || row.dayOfWeek() > 7) {
|
||
throw new IllegalArgumentException("Курс, дата, неделя и день недели обязательны");
|
||
}
|
||
if (row.date().isBefore(calendar.getAcademicYear().getStartDate())
|
||
|| row.date().isAfter(calendar.getAcademicYear().getEndDate())) {
|
||
throw new IllegalArgumentException("Дата выходит за пределы учебного года");
|
||
}
|
||
if (row.courseNumber() > calendar.getCourseCount()) {
|
||
throw new IllegalArgumentException("Номер курса выходит за пределы графика");
|
||
}
|
||
|
||
AcademicCalendarActivityType activityType = resolveActivityType(row);
|
||
AcademicCalendarDay day = new AcademicCalendarDay();
|
||
day.setAcademicCalendar(calendar);
|
||
day.setCourseNumber(row.courseNumber());
|
||
day.setDate(row.date());
|
||
day.setWeekNumber(row.weekNumber());
|
||
day.setDayOfWeek(row.dayOfWeek());
|
||
day.setActivityType(activityType);
|
||
return day;
|
||
}
|
||
|
||
private AcademicCalendarActivityType resolveActivityType(AcademicCalendarGridDayDto row) {
|
||
if (row.activityTypeId() != null) {
|
||
return activityTypeRepository.findById(row.activityTypeId())
|
||
.orElseThrow(() -> new IllegalArgumentException("Код активности не найден"));
|
||
}
|
||
if (row.activityCode() != null && !row.activityCode().isBlank()) {
|
||
return activityTypeRepository.findByCode(row.activityCode().trim())
|
||
.orElseThrow(() -> new IllegalArgumentException("Код активности не найден"));
|
||
}
|
||
throw new IllegalArgumentException("Код активности обязателен");
|
||
}
|
||
|
||
private AcademicCalendarDto toCalendarDto(AcademicCalendar calendar) {
|
||
AcademicYear year = calendar.getAcademicYear();
|
||
Speciality speciality = calendar.getSpeciality();
|
||
SpecialtyProfile profile = calendar.getSpecialtyProfile();
|
||
EducationForm studyForm = calendar.getStudyForm();
|
||
return new AcademicCalendarDto(
|
||
calendar.getId(),
|
||
calendar.getTitle(),
|
||
year.getId(),
|
||
year.getTitle(),
|
||
year.getStartDate(),
|
||
year.getEndDate(),
|
||
speciality.getId(),
|
||
speciality.getSpecialityCode(),
|
||
speciality.getSpecialityName(),
|
||
profile.getId(),
|
||
profile.getName(),
|
||
studyForm.getId(),
|
||
studyForm.getName(),
|
||
calendar.getCourseCount()
|
||
);
|
||
}
|
||
|
||
private AcademicCalendarGridDayDto toGridDayDto(AcademicCalendarDay day) {
|
||
AcademicCalendarActivityType activityType = day.getActivityType();
|
||
return new AcademicCalendarGridDayDto(
|
||
day.getId(),
|
||
day.getAcademicCalendar().getId(),
|
||
day.getCourseNumber(),
|
||
day.getDate(),
|
||
day.getWeekNumber(),
|
||
day.getDayOfWeek(),
|
||
activityType.getId(),
|
||
activityType.getCode(),
|
||
activityType.getName(),
|
||
activityType.getAllowSchedule()
|
||
);
|
||
}
|
||
}
|