создал систему календарного учебного графика
This commit is contained in:
@@ -5,7 +5,6 @@ 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.Comparator;
|
||||
@@ -18,19 +17,19 @@ public class AcademicCalendarAdminController {
|
||||
|
||||
private final AcademicYearRepository academicYearRepository;
|
||||
private final SemesterRepository semesterRepository;
|
||||
private final HolidayRepository holidayRepository;
|
||||
private final AcademicCalendarMatrixRepository calendarMatrixRepository;
|
||||
private final AcademicCalendarActivityTypeRepository activityTypeRepository;
|
||||
private final AcademicCalendarDayRepository calendarDayRepository;
|
||||
private final ScheduleGeneratorService scheduleGeneratorService;
|
||||
|
||||
public AcademicCalendarAdminController(AcademicYearRepository academicYearRepository,
|
||||
SemesterRepository semesterRepository,
|
||||
HolidayRepository holidayRepository,
|
||||
AcademicCalendarMatrixRepository calendarMatrixRepository,
|
||||
AcademicCalendarActivityTypeRepository activityTypeRepository,
|
||||
AcademicCalendarDayRepository calendarDayRepository,
|
||||
ScheduleGeneratorService scheduleGeneratorService) {
|
||||
this.academicYearRepository = academicYearRepository;
|
||||
this.semesterRepository = semesterRepository;
|
||||
this.holidayRepository = holidayRepository;
|
||||
this.calendarMatrixRepository = calendarMatrixRepository;
|
||||
this.activityTypeRepository = activityTypeRepository;
|
||||
this.calendarDayRepository = calendarDayRepository;
|
||||
this.scheduleGeneratorService = scheduleGeneratorService;
|
||||
}
|
||||
|
||||
@@ -128,113 +127,60 @@ public class AcademicCalendarAdminController {
|
||||
return ResponseEntity.ok(toSemesterDto(semesterRepository.save(semester)));
|
||||
}
|
||||
|
||||
@GetMapping("/holidays")
|
||||
public ResponseEntity<?> getHolidays(@RequestParam Long academicYearId) {
|
||||
if (!academicYearRepository.existsById(academicYearId)) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", "Учебный год не найден"));
|
||||
}
|
||||
return ResponseEntity.ok(holidayRepository.findByAcademicYearIdOrderByDateAsc(academicYearId).stream()
|
||||
.map(this::toHolidayDto)
|
||||
.toList());
|
||||
@GetMapping("/activity-types")
|
||||
public List<AcademicCalendarActivityTypeDto> getActivityTypes() {
|
||||
return activityTypeRepository.findAllByOrderByDisplayOrderAscCodeAsc().stream()
|
||||
.map(this::toActivityTypeDto)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@PostMapping("/holidays")
|
||||
public ResponseEntity<?> createHoliday(@RequestBody HolidayDto request) {
|
||||
AcademicYear year = academicYearRepository.findById(request.academicYearId()).orElse(null);
|
||||
if (year == null) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", "Учебный год не найден"));
|
||||
@PostMapping("/activity-types")
|
||||
public ResponseEntity<?> createActivityType(@RequestBody AcademicCalendarActivityTypeDto request) {
|
||||
String validationError = validateActivityType(request);
|
||||
if (validationError != null) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", validationError));
|
||||
}
|
||||
if (request.date() == null) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", "Дата праздника обязательна"));
|
||||
if (activityTypeRepository.findByCode(request.code().trim()).isPresent()) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", "Код активности уже существует"));
|
||||
}
|
||||
if (holidayRepository.existsByAcademicYearIdAndDate(year.getId(), request.date())) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", "Эта дата уже добавлена в исключения"));
|
||||
}
|
||||
|
||||
Holiday holiday = new Holiday();
|
||||
holiday.setAcademicYear(year);
|
||||
holiday.setDate(request.date());
|
||||
holiday.setDescription(request.description());
|
||||
AcademicCalendarActivityType activityType = new AcademicCalendarActivityType();
|
||||
applyActivityType(activityType, request);
|
||||
scheduleGeneratorService.clearCache();
|
||||
return ResponseEntity.ok(toHolidayDto(holidayRepository.save(holiday)));
|
||||
return ResponseEntity.ok(toActivityTypeDto(activityTypeRepository.save(activityType)));
|
||||
}
|
||||
|
||||
@PutMapping("/holidays/{id}")
|
||||
public ResponseEntity<?> updateHoliday(@PathVariable Long id, @RequestBody HolidayDto request) {
|
||||
Holiday holiday = holidayRepository.findById(id).orElse(null);
|
||||
if (holiday == null) {
|
||||
@PutMapping("/activity-types/{id}")
|
||||
public ResponseEntity<?> updateActivityType(@PathVariable Long id,
|
||||
@RequestBody AcademicCalendarActivityTypeDto request) {
|
||||
AcademicCalendarActivityType activityType = activityTypeRepository.findById(id).orElse(null);
|
||||
if (activityType == null) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
if (request.date() == null) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", "Дата праздника обязательна"));
|
||||
String validationError = validateActivityType(request);
|
||||
if (validationError != null) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", validationError));
|
||||
}
|
||||
holiday.setDate(request.date());
|
||||
holiday.setDescription(request.description());
|
||||
if (activityTypeRepository.findByCode(request.code().trim())
|
||||
.filter(existing -> !existing.getId().equals(id))
|
||||
.isPresent()) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", "Код активности уже существует"));
|
||||
}
|
||||
applyActivityType(activityType, request);
|
||||
scheduleGeneratorService.clearCache();
|
||||
return ResponseEntity.ok(toHolidayDto(holidayRepository.save(holiday)));
|
||||
return ResponseEntity.ok(toActivityTypeDto(activityTypeRepository.save(activityType)));
|
||||
}
|
||||
|
||||
@DeleteMapping("/holidays/{id}")
|
||||
public ResponseEntity<?> deleteHoliday(@PathVariable Long id) {
|
||||
if (!holidayRepository.existsById(id)) {
|
||||
@DeleteMapping("/activity-types/{id}")
|
||||
public ResponseEntity<?> deleteActivityType(@PathVariable Long id) {
|
||||
if (!activityTypeRepository.existsById(id)) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
holidayRepository.deleteById(id);
|
||||
if (calendarDayRepository.existsByActivityTypeId(id)) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", "Код активности используется в календарных графиках"));
|
||||
}
|
||||
activityTypeRepository.deleteById(id);
|
||||
scheduleGeneratorService.clearCache();
|
||||
return ResponseEntity.ok(Map.of("message", "Праздник удалён"));
|
||||
}
|
||||
|
||||
@GetMapping("/matrix")
|
||||
public ResponseEntity<?> getMatrix(@RequestParam Long semesterId,
|
||||
@RequestParam Integer courseNumber,
|
||||
@RequestParam Long specialtyId) {
|
||||
if (!semesterRepository.existsById(semesterId)) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", "Семестр не найден"));
|
||||
}
|
||||
return ResponseEntity.ok(calendarMatrixRepository
|
||||
.findBySemesterIdAndCourseNumberAndSpecialtyIdOrderByWeekNumberAsc(semesterId, courseNumber, specialtyId)
|
||||
.stream()
|
||||
.map(this::toMatrixDto)
|
||||
.toList());
|
||||
}
|
||||
|
||||
@PutMapping("/matrix")
|
||||
@Transactional
|
||||
public ResponseEntity<?> saveMatrix(@RequestBody List<AcademicCalendarMatrixDto> rows) {
|
||||
if (rows == null || rows.isEmpty()) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", "Передайте хотя бы одну строку матрицы"));
|
||||
}
|
||||
|
||||
try {
|
||||
for (AcademicCalendarMatrixDto row : rows) {
|
||||
Semester semester = semesterRepository.findById(row.semesterId())
|
||||
.orElseThrow(() -> new IllegalArgumentException("Семестр не найден"));
|
||||
if (row.courseNumber() == null || row.courseNumber() <= 0
|
||||
|| row.specialtyId() == null
|
||||
|| row.weekNumber() == null || row.weekNumber() <= 0) {
|
||||
throw new IllegalArgumentException("Курс, специальность и номер недели обязательны");
|
||||
}
|
||||
AcademicCalendarMatrix matrix = calendarMatrixRepository
|
||||
.findBySemesterIdAndCourseNumberAndSpecialtyIdAndWeekNumber(
|
||||
row.semesterId(),
|
||||
row.courseNumber(),
|
||||
row.specialtyId(),
|
||||
row.weekNumber()
|
||||
)
|
||||
.orElseGet(AcademicCalendarMatrix::new);
|
||||
matrix.setSemester(semester);
|
||||
matrix.setCourseNumber(row.courseNumber());
|
||||
matrix.setSpecialtyId(row.specialtyId());
|
||||
matrix.setWeekNumber(row.weekNumber());
|
||||
matrix.setActivityType(row.activityType() == null ? AcademicActivityType.THEORY : row.activityType());
|
||||
calendarMatrixRepository.save(matrix);
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", e.getMessage()));
|
||||
}
|
||||
|
||||
scheduleGeneratorService.clearCache();
|
||||
return ResponseEntity.ok(Map.of("message", "Матрица учебного графика сохранена"));
|
||||
return ResponseEntity.ok(Map.of("message", "Код активности удалён"));
|
||||
}
|
||||
|
||||
private String validateYear(AcademicYearDto request) {
|
||||
@@ -263,6 +209,19 @@ public class AcademicCalendarAdminController {
|
||||
return null;
|
||||
}
|
||||
|
||||
private String validateActivityType(AcademicCalendarActivityTypeDto request) {
|
||||
if (request == null) {
|
||||
return "Передайте код активности";
|
||||
}
|
||||
if (request.code() == null || request.code().isBlank()) {
|
||||
return "Код активности обязателен";
|
||||
}
|
||||
if (request.name() == null || request.name().isBlank()) {
|
||||
return "Название активности обязательно";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void applyYear(AcademicYear year, AcademicYearDto request) {
|
||||
year.setTitle(request.title().trim());
|
||||
year.setStartDate(request.startDate());
|
||||
@@ -275,6 +234,19 @@ public class AcademicCalendarAdminController {
|
||||
semester.setEndDate(request.endDate());
|
||||
}
|
||||
|
||||
private void applyActivityType(AcademicCalendarActivityType activityType, AcademicCalendarActivityTypeDto request) {
|
||||
activityType.setCode(request.code().trim());
|
||||
activityType.setName(request.name().trim());
|
||||
activityType.setAllowSchedule(Boolean.TRUE.equals(request.allowSchedule()));
|
||||
activityType.setColorCode(request.colorCode() == null || request.colorCode().isBlank()
|
||||
? "#64748b"
|
||||
: request.colorCode().trim());
|
||||
activityType.setDisplayOrder(request.displayOrder() == null ? 100 : request.displayOrder());
|
||||
activityType.setDescription(request.description() == null || request.description().isBlank()
|
||||
? null
|
||||
: request.description().trim());
|
||||
}
|
||||
|
||||
private AcademicYearDto toAcademicYearDto(AcademicYear year) {
|
||||
List<SemesterDto> semesters = semesterRepository.findByAcademicYearIdOrderByStartDateAsc(year.getId())
|
||||
.stream()
|
||||
@@ -294,24 +266,15 @@ public class AcademicCalendarAdminController {
|
||||
);
|
||||
}
|
||||
|
||||
private HolidayDto toHolidayDto(Holiday holiday) {
|
||||
return new HolidayDto(
|
||||
holiday.getId(),
|
||||
holiday.getDate(),
|
||||
holiday.getAcademicYear().getId(),
|
||||
holiday.getAcademicYear().getTitle(),
|
||||
holiday.getDescription()
|
||||
);
|
||||
}
|
||||
|
||||
private AcademicCalendarMatrixDto toMatrixDto(AcademicCalendarMatrix matrix) {
|
||||
return new AcademicCalendarMatrixDto(
|
||||
matrix.getId(),
|
||||
matrix.getSemester().getId(),
|
||||
matrix.getCourseNumber(),
|
||||
matrix.getSpecialtyId(),
|
||||
matrix.getWeekNumber(),
|
||||
matrix.getActivityType()
|
||||
private AcademicCalendarActivityTypeDto toActivityTypeDto(AcademicCalendarActivityType activityType) {
|
||||
return new AcademicCalendarActivityTypeDto(
|
||||
activityType.getId(),
|
||||
activityType.getCode(),
|
||||
activityType.getName(),
|
||||
activityType.getAllowSchedule(),
|
||||
activityType.getColorCode(),
|
||||
activityType.getDisplayOrder(),
|
||||
activityType.getDescription()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
package com.magistr.app.controller;
|
||||
|
||||
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")
|
||||
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 CalendarStudyFormRepository studyFormRepository;
|
||||
private final ScheduleGeneratorService scheduleGeneratorService;
|
||||
|
||||
public AcademicCalendarController(AcademicCalendarRepository calendarRepository,
|
||||
AcademicCalendarDayRepository calendarDayRepository,
|
||||
AcademicCalendarActivityTypeRepository activityTypeRepository,
|
||||
AcademicYearRepository academicYearRepository,
|
||||
SpecialtiesRepository specialtiesRepository,
|
||||
SpecialtyProfileRepository profileRepository,
|
||||
CalendarStudyFormRepository studyFormRepository,
|
||||
ScheduleGeneratorService scheduleGeneratorService) {
|
||||
this.calendarRepository = calendarRepository;
|
||||
this.calendarDayRepository = calendarDayRepository;
|
||||
this.activityTypeRepository = activityTypeRepository;
|
||||
this.academicYearRepository = academicYearRepository;
|
||||
this.specialtiesRepository = specialtiesRepository;
|
||||
this.profileRepository = profileRepository;
|
||||
this.studyFormRepository = studyFormRepository;
|
||||
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("/study-forms")
|
||||
public List<CalendarStudyFormDto> getStudyForms() {
|
||||
return studyFormRepository.findAllByOrderByNameAsc().stream()
|
||||
.map(form -> new CalendarStudyFormDto(form.getId(), form.getName(), form.getDescription()))
|
||||
.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("Профиль не относится к выбранной специальности");
|
||||
}
|
||||
CalendarStudyForm studyForm = studyFormRepository.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();
|
||||
CalendarStudyForm 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()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,17 @@
|
||||
package com.magistr.app.controller;
|
||||
|
||||
import com.magistr.app.dto.CreateGroupRequest;
|
||||
import com.magistr.app.dto.GroupCalendarAssignmentDto;
|
||||
import com.magistr.app.dto.GroupResponse;
|
||||
import com.magistr.app.model.AcademicCalendar;
|
||||
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.repository.EducationFormRepository;
|
||||
import com.magistr.app.repository.GroupRepository;
|
||||
import com.magistr.app.model.StudentGroupCalendarAssignment;
|
||||
import com.magistr.app.repository.*;
|
||||
import com.magistr.app.service.ScheduleGeneratorService;
|
||||
import com.magistr.app.utils.CourseAndSemesterCalculator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -13,7 +19,6 @@ import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.Year;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@@ -26,11 +31,29 @@ public class GroupController {
|
||||
|
||||
private final GroupRepository groupRepository;
|
||||
private final EducationFormRepository educationFormRepository;
|
||||
private final SpecialtiesRepository specialtiesRepository;
|
||||
private final SpecialtyProfileRepository specialtyProfileRepository;
|
||||
private final AcademicYearRepository academicYearRepository;
|
||||
private final AcademicCalendarRepository academicCalendarRepository;
|
||||
private final StudentGroupCalendarAssignmentRepository assignmentRepository;
|
||||
private final ScheduleGeneratorService scheduleGeneratorService;
|
||||
|
||||
public GroupController(GroupRepository groupRepository,
|
||||
EducationFormRepository educationFormRepository) {
|
||||
EducationFormRepository educationFormRepository,
|
||||
SpecialtiesRepository specialtiesRepository,
|
||||
SpecialtyProfileRepository specialtyProfileRepository,
|
||||
AcademicYearRepository academicYearRepository,
|
||||
AcademicCalendarRepository academicCalendarRepository,
|
||||
StudentGroupCalendarAssignmentRepository assignmentRepository,
|
||||
ScheduleGeneratorService scheduleGeneratorService) {
|
||||
this.groupRepository = groupRepository;
|
||||
this.educationFormRepository = educationFormRepository;
|
||||
this.specialtiesRepository = specialtiesRepository;
|
||||
this.specialtyProfileRepository = specialtyProfileRepository;
|
||||
this.academicYearRepository = academicYearRepository;
|
||||
this.academicCalendarRepository = academicCalendarRepository;
|
||||
this.assignmentRepository = assignmentRepository;
|
||||
this.scheduleGeneratorService = scheduleGeneratorService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@@ -41,21 +64,7 @@ public class GroupController {
|
||||
List<StudentGroup> groups = groupRepository.findAll();
|
||||
|
||||
List<GroupResponse> response = groups.stream()
|
||||
.map(g -> {
|
||||
int course = CourseAndSemesterCalculator.getActualCourse(g.getYearStartStudy());
|
||||
int semester = CourseAndSemesterCalculator.getActualSemester(g.getYearStartStudy());
|
||||
return new GroupResponse(
|
||||
g.getId(),
|
||||
g.getName(),
|
||||
g.getGroupSize(),
|
||||
g.getEducationForm().getId(),
|
||||
g.getEducationForm().getName(),
|
||||
g.getDepartmentId(),
|
||||
course,
|
||||
semester,
|
||||
g.getSpecialityCode()
|
||||
);
|
||||
})
|
||||
.map(this::mapToResponse)
|
||||
.toList();
|
||||
logger.info("Получено {} групп", response.size());
|
||||
return response;
|
||||
@@ -78,21 +87,7 @@ public class GroupController {
|
||||
}
|
||||
|
||||
List<GroupResponse> response = groups.stream()
|
||||
.map(g -> {
|
||||
int course = CourseAndSemesterCalculator.getActualCourse(g.getYearStartStudy());
|
||||
int semester = CourseAndSemesterCalculator.getActualSemester(g.getYearStartStudy());
|
||||
return new GroupResponse(
|
||||
g.getId(),
|
||||
g.getName(),
|
||||
g.getGroupSize(),
|
||||
g.getEducationForm().getId(),
|
||||
g.getEducationForm().getName(),
|
||||
g.getDepartmentId(),
|
||||
course,
|
||||
semester,
|
||||
g.getSpecialityCode()
|
||||
);
|
||||
})
|
||||
.map(this::mapToResponse)
|
||||
.toList();
|
||||
|
||||
logger.info("Найдено {} групп для кафедры с ID - {}", response.size(), departmentId);
|
||||
@@ -110,51 +105,25 @@ public class GroupController {
|
||||
logger.info("Получен запрос на создание новой группы: name = {}, groupSize = {}, educationFormId = {}, departmentId = {}, yearStartStudy = {}",
|
||||
request.getName(), request.getGroupSize(), request.getEducationFormId(), request.getDepartmentId(), request.getYearStartStudy());
|
||||
try {
|
||||
if (request.getName() == null || request.getName().isBlank()) {
|
||||
String errorMessage = "Название группы обязательно";
|
||||
logger.error("Ошибка валидации: {}", errorMessage);
|
||||
return ResponseEntity.badRequest().body(Map.of("message", errorMessage));
|
||||
}
|
||||
if (groupRepository.findByName(request.getName().trim()).isPresent()) {
|
||||
String errorMessage = "Группа с таким названием уже существует";
|
||||
logger.error("Ошибка валидации: {}", errorMessage);
|
||||
return ResponseEntity.badRequest().body(Map.of("message", errorMessage));
|
||||
}
|
||||
if (request.getGroupSize() == null) {
|
||||
String errorMessage = "Численность группы обязательна";
|
||||
logger.error("Ошибка валидации: {}", errorMessage);
|
||||
return ResponseEntity.badRequest().body(Map.of("message", errorMessage));
|
||||
}
|
||||
if (request.getEducationFormId() == null) {
|
||||
String errorMessage = "Форма обучения обязательна";
|
||||
logger.error("Ошибка валидации: {}", errorMessage);
|
||||
return ResponseEntity.badRequest().body(Map.of("message", errorMessage));
|
||||
}
|
||||
if (request.getDepartmentId() == null || request.getDepartmentId() == 0) {
|
||||
String errorMessage = "ID кафедры обязателен";
|
||||
logger.error("Ошибка валидации: {}", errorMessage);
|
||||
return ResponseEntity.badRequest().body(Map.of("message", errorMessage));
|
||||
}
|
||||
// if (request.getCourse() == null || request.getCourse() == 0) {
|
||||
// String errorMessage = "Курс обязателен";
|
||||
// logger.error("Ошибка валидации: {}", errorMessage);
|
||||
// return ResponseEntity.badRequest().body(Map.of("message", errorMessage));
|
||||
// }
|
||||
if (request.getYearStartStudy() == null || request.getYearStartStudy() == 0) {
|
||||
String errorMessage = "Год начала обучения обязателен";
|
||||
logger.error("Ошибка валидации: {}", errorMessage);
|
||||
return ResponseEntity.badRequest().body(Map.of("message", errorMessage));
|
||||
}
|
||||
if (request.getSpecialityCode() == null || request.getSpecialityCode() == 0) {
|
||||
String errorMessage = "Код специальности обязателен";
|
||||
logger.error("Ошибка валидации: {}", errorMessage);
|
||||
return ResponseEntity.badRequest().body(Map.of("message", errorMessage));
|
||||
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());
|
||||
@@ -162,20 +131,13 @@ public class GroupController {
|
||||
group.setEducationForm(efOpt.get());
|
||||
group.setDepartmentId(request.getDepartmentId());
|
||||
group.setYearStartStudy(request.getYearStartStudy());
|
||||
group.setSpecialityCode(request.getSpecialityCode());
|
||||
group.setSpeciality(speciality);
|
||||
group.setSpecialtyProfile(profile);
|
||||
groupRepository.save(group);
|
||||
|
||||
logger.info("Группа успешно создана с ID - {}", group.getId());
|
||||
|
||||
return ResponseEntity.ok(new GroupResponse(
|
||||
group.getId(),
|
||||
group.getName(),
|
||||
group.getGroupSize(),
|
||||
group.getEducationForm().getId(),
|
||||
group.getEducationForm().getName(),
|
||||
group.getDepartmentId(),
|
||||
group.getYearStartStudy(),
|
||||
group.getSpecialityCode()));
|
||||
return ResponseEntity.ok(mapToResponse(group));
|
||||
} catch (Exception e ) {
|
||||
logger.error("Ошибка при создании группы: {}", e.getMessage(), e);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
@@ -183,6 +145,55 @@ public class GroupController {
|
||||
}
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ResponseEntity<?> updateGroup(@PathVariable Long id, @RequestBody CreateGroupRequest request) {
|
||||
logger.info("Получен запрос на обновление группы с ID - {}", id);
|
||||
try {
|
||||
Optional<StudentGroup> groupOpt = groupRepository.findById(id);
|
||||
if (groupOpt.isEmpty()) {
|
||||
logger.info("Группа с ID - {} не найдена", id);
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
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 = groupOpt.get();
|
||||
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 - {} успешно обновлена", id);
|
||||
return ResponseEntity.ok(mapToResponse(group));
|
||||
} catch (Exception e) {
|
||||
logger.error("Ошибка при обновлении группы с ID - {}: {}", id, e.getMessage(), e);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body(Map.of("message", "Произошла ошибка при обновлении группы: " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<?> deleteGroup(@PathVariable Long id) {
|
||||
logger.info("Получен запрос на удаление группы с ID - {}", id);
|
||||
@@ -194,4 +205,126 @@ public class GroupController {
|
||||
logger.info("Группа с ID - {} успешно удалена", id);
|
||||
return ResponseEntity.ok(Map.of("message", "Группа удалена"));
|
||||
}
|
||||
|
||||
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) {
|
||||
int course = CourseAndSemesterCalculator.getActualCourse(group.getYearStartStudy());
|
||||
int semester = CourseAndSemesterCalculator.getActualSemester(group.getYearStartStudy());
|
||||
Speciality speciality = group.getSpeciality();
|
||||
SpecialtyProfile profile = group.getSpecialtyProfile();
|
||||
return new GroupResponse(
|
||||
group.getId(),
|
||||
group.getName(),
|
||||
group.getGroupSize(),
|
||||
group.getEducationForm().getId(),
|
||||
group.getEducationForm().getName(),
|
||||
group.getDepartmentId(),
|
||||
group.getYearStartStudy(),
|
||||
course,
|
||||
semester,
|
||||
speciality.getId(),
|
||||
speciality.getSpecialityCode(),
|
||||
speciality.getSpecialityName(),
|
||||
profile.getId(),
|
||||
profile.getName()
|
||||
);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}/calendar-assignments")
|
||||
public ResponseEntity<?> getCalendarAssignments(@PathVariable Long id) {
|
||||
if (!groupRepository.existsById(id)) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
return ResponseEntity.ok(assignmentRepository.findByGroupIdWithDetails(id).stream()
|
||||
.map(this::toAssignmentDto)
|
||||
.toList());
|
||||
}
|
||||
|
||||
@PutMapping("/{id}/calendar-assignments")
|
||||
public ResponseEntity<?> saveCalendarAssignment(@PathVariable Long id,
|
||||
@RequestBody GroupCalendarAssignmentDto request) {
|
||||
if (request == null || request.academicYearId() == null || request.calendarId() == null) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", "Учебный год и календарный график обязательны"));
|
||||
}
|
||||
StudentGroup group = groupRepository.findById(id).orElse(null);
|
||||
if (group == null) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
AcademicYear academicYear = academicYearRepository.findById(request.academicYearId()).orElse(null);
|
||||
if (academicYear == null) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", "Учебный год не найден"));
|
||||
}
|
||||
AcademicCalendar calendar = academicCalendarRepository.findByIdWithDetails(request.calendarId()).orElse(null);
|
||||
if (calendar == null) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", "Календарный график не найден"));
|
||||
}
|
||||
if (!calendar.getAcademicYear().getId().equals(academicYear.getId())) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", "График относится к другому учебному году"));
|
||||
}
|
||||
if (!calendar.getSpeciality().getId().equals(group.getSpeciality().getId())
|
||||
|| !calendar.getSpecialtyProfile().getId().equals(group.getSpecialtyProfile().getId())) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", "График не соответствует специальности и профилю группы"));
|
||||
}
|
||||
|
||||
StudentGroupCalendarAssignment assignment = assignmentRepository
|
||||
.findByGroupIdAndAcademicYearIdWithDetails(group.getId(), academicYear.getId())
|
||||
.orElseGet(StudentGroupCalendarAssignment::new);
|
||||
assignment.setStudentGroup(group);
|
||||
assignment.setAcademicYear(academicYear);
|
||||
assignment.setAcademicCalendar(calendar);
|
||||
scheduleGeneratorService.clearCache();
|
||||
return ResponseEntity.ok(toAssignmentDto(assignmentRepository.save(assignment)));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}/calendar-assignments/{assignmentId}")
|
||||
public ResponseEntity<?> deleteCalendarAssignment(@PathVariable Long id, @PathVariable Long assignmentId) {
|
||||
StudentGroupCalendarAssignment assignment = assignmentRepository.findById(assignmentId).orElse(null);
|
||||
if (assignment == null || !assignment.getStudentGroup().getId().equals(id)) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
assignmentRepository.delete(assignment);
|
||||
scheduleGeneratorService.clearCache();
|
||||
return ResponseEntity.ok(Map.of("message", "Назначение календарного графика удалено"));
|
||||
}
|
||||
|
||||
private GroupCalendarAssignmentDto toAssignmentDto(StudentGroupCalendarAssignment assignment) {
|
||||
return new GroupCalendarAssignmentDto(
|
||||
assignment.getId(),
|
||||
assignment.getStudentGroup().getId(),
|
||||
assignment.getStudentGroup().getName(),
|
||||
assignment.getAcademicYear().getId(),
|
||||
assignment.getAcademicYear().getTitle(),
|
||||
assignment.getAcademicCalendar().getId(),
|
||||
assignment.getAcademicCalendar().getTitle()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,11 @@ package com.magistr.app.controller;
|
||||
|
||||
import com.magistr.app.dto.CreateSpecialityRequest;
|
||||
import com.magistr.app.dto.SpecialityResponse;
|
||||
import com.magistr.app.dto.SpecialtyProfileDto;
|
||||
import com.magistr.app.model.Speciality;
|
||||
import com.magistr.app.model.SpecialtyProfile;
|
||||
import com.magistr.app.repository.SpecialtiesRepository;
|
||||
import com.magistr.app.repository.SpecialtyProfileRepository;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
@@ -20,9 +23,12 @@ public class SpecialityController {
|
||||
private static final Logger logger = LoggerFactory.getLogger(SpecialityController.class);
|
||||
|
||||
private final SpecialtiesRepository specialtiesRepository;
|
||||
private final SpecialtyProfileRepository specialtyProfileRepository;
|
||||
|
||||
public SpecialityController(SpecialtiesRepository specialtiesRepository) {
|
||||
public SpecialityController(SpecialtiesRepository specialtiesRepository,
|
||||
SpecialtyProfileRepository specialtyProfileRepository) {
|
||||
this.specialtiesRepository = specialtiesRepository;
|
||||
this.specialtyProfileRepository = specialtyProfileRepository;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@@ -75,6 +81,10 @@ public class SpecialityController {
|
||||
speciality.setSpecialityName(request.getSpecialityName());
|
||||
speciality.setSpecialityCode(request.getSpecialityCode());
|
||||
specialtiesRepository.save(speciality);
|
||||
SpecialtyProfile defaultProfile = new SpecialtyProfile();
|
||||
defaultProfile.setSpeciality(speciality);
|
||||
defaultProfile.setName("Без профиля");
|
||||
specialtyProfileRepository.save(defaultProfile);
|
||||
|
||||
logger.info("Специальность успешно создана с ID: {}", speciality.getId());
|
||||
|
||||
@@ -159,4 +169,98 @@ public class SpecialityController {
|
||||
logger.info("Специальность с ID - {} успешно удалена", id);
|
||||
return ResponseEntity.ok(Map.of("message", "Специальность удалена"));
|
||||
}
|
||||
|
||||
@GetMapping("/{specialtyId}/profiles")
|
||||
public ResponseEntity<?> getProfiles(@PathVariable Long specialtyId) {
|
||||
if (!specialtiesRepository.existsById(specialtyId)) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
return ResponseEntity.ok(specialtyProfileRepository.findBySpecialityIdOrderByNameAsc(specialtyId).stream()
|
||||
.map(this::toProfileDto)
|
||||
.toList());
|
||||
}
|
||||
|
||||
@PostMapping("/{specialtyId}/profiles")
|
||||
public ResponseEntity<?> createProfile(@PathVariable Long specialtyId, @RequestBody SpecialtyProfileDto request) {
|
||||
Speciality speciality = specialtiesRepository.findById(specialtyId).orElse(null);
|
||||
if (speciality == null) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
String validationError = validateProfile(specialtyId, null, request);
|
||||
if (validationError != null) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", validationError));
|
||||
}
|
||||
|
||||
SpecialtyProfile profile = new SpecialtyProfile();
|
||||
profile.setSpeciality(speciality);
|
||||
profile.setName(request.name().trim());
|
||||
profile.setDescription(trimToNull(request.description()));
|
||||
return ResponseEntity.ok(toProfileDto(specialtyProfileRepository.save(profile)));
|
||||
}
|
||||
|
||||
@PutMapping("/{specialtyId}/profiles/{profileId}")
|
||||
public ResponseEntity<?> updateProfile(@PathVariable Long specialtyId,
|
||||
@PathVariable Long profileId,
|
||||
@RequestBody SpecialtyProfileDto request) {
|
||||
SpecialtyProfile profile = specialtyProfileRepository.findById(profileId).orElse(null);
|
||||
if (profile == null || !profile.getSpeciality().getId().equals(specialtyId)) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
String validationError = validateProfile(specialtyId, profileId, request);
|
||||
if (validationError != null) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", validationError));
|
||||
}
|
||||
|
||||
profile.setName(request.name().trim());
|
||||
profile.setDescription(trimToNull(request.description()));
|
||||
return ResponseEntity.ok(toProfileDto(specialtyProfileRepository.save(profile)));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{specialtyId}/profiles/{profileId}")
|
||||
public ResponseEntity<?> deleteProfile(@PathVariable Long specialtyId, @PathVariable Long profileId) {
|
||||
SpecialtyProfile profile = specialtyProfileRepository.findById(profileId).orElse(null);
|
||||
if (profile == null || !profile.getSpeciality().getId().equals(specialtyId)) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
try {
|
||||
specialtyProfileRepository.delete(profile);
|
||||
return ResponseEntity.ok(Map.of("message", "Профиль обучения удалён"));
|
||||
} catch (Exception e) {
|
||||
logger.error("Ошибка при удалении профиля обучения с ID - {}: {}", profileId, e.getMessage(), e);
|
||||
return ResponseEntity.badRequest()
|
||||
.body(Map.of("message", "Нельзя удалить профиль, который используется группами или календарными графиками"));
|
||||
}
|
||||
}
|
||||
|
||||
private String validateProfile(Long specialtyId, Long profileId, SpecialtyProfileDto request) {
|
||||
if (request == null) {
|
||||
return "Передайте данные профиля";
|
||||
}
|
||||
if (request.name() == null || request.name().isBlank()) {
|
||||
return "Название профиля обязательно";
|
||||
}
|
||||
return specialtyProfileRepository.findBySpecialityIdAndName(specialtyId, request.name().trim())
|
||||
.filter(existing -> !existing.getId().equals(profileId))
|
||||
.map(existing -> "Профиль с таким названием уже существует")
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private SpecialtyProfileDto toProfileDto(SpecialtyProfile profile) {
|
||||
Speciality speciality = profile.getSpeciality();
|
||||
return new SpecialtyProfileDto(
|
||||
profile.getId(),
|
||||
speciality.getId(),
|
||||
speciality.getSpecialityCode(),
|
||||
speciality.getSpecialityName(),
|
||||
profile.getName(),
|
||||
profile.getDescription()
|
||||
);
|
||||
}
|
||||
|
||||
private String trimToNull(String value) {
|
||||
if (value == null || value.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
return value.trim();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.magistr.app.dto;
|
||||
|
||||
public record AcademicCalendarActivityTypeDto(
|
||||
Long id,
|
||||
String code,
|
||||
String name,
|
||||
Boolean allowSchedule,
|
||||
String colorCode,
|
||||
Integer displayOrder,
|
||||
String description
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.magistr.app.dto;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
public record AcademicCalendarDto(
|
||||
Long id,
|
||||
String title,
|
||||
Long academicYearId,
|
||||
String academicYearTitle,
|
||||
LocalDate academicYearStartDate,
|
||||
LocalDate academicYearEndDate,
|
||||
Long specialtyId,
|
||||
String specialtyCode,
|
||||
String specialtyName,
|
||||
Long specialtyProfileId,
|
||||
String specialtyProfileName,
|
||||
Long studyFormId,
|
||||
String studyFormName,
|
||||
Integer courseCount
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.magistr.app.dto;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
public record AcademicCalendarGridDayDto(
|
||||
Long id,
|
||||
Long calendarId,
|
||||
Integer courseNumber,
|
||||
LocalDate date,
|
||||
Integer weekNumber,
|
||||
Integer dayOfWeek,
|
||||
Long activityTypeId,
|
||||
String activityCode,
|
||||
String activityName,
|
||||
Boolean allowSchedule
|
||||
) {
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package com.magistr.app.dto;
|
||||
|
||||
import com.magistr.app.model.AcademicActivityType;
|
||||
|
||||
public record AcademicCalendarMatrixDto(
|
||||
Long id,
|
||||
Long semesterId,
|
||||
Integer courseNumber,
|
||||
Long specialtyId,
|
||||
Integer weekNumber,
|
||||
AcademicActivityType activityType
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.magistr.app.dto;
|
||||
|
||||
public record CalendarStudyFormDto(
|
||||
Long id,
|
||||
String name,
|
||||
String description
|
||||
) {
|
||||
}
|
||||
@@ -7,6 +7,8 @@ public class CreateGroupRequest {
|
||||
private Long educationFormId;
|
||||
private Long departmentId;
|
||||
private Integer yearStartStudy;
|
||||
private Long specialtyId;
|
||||
private Long specialtyProfileId;
|
||||
private Long specialityCode;
|
||||
|
||||
public String getName() {
|
||||
@@ -56,4 +58,24 @@ public class CreateGroupRequest {
|
||||
public void setSpecialityCode(Long specialityCode) {
|
||||
this.specialityCode = specialityCode;
|
||||
}
|
||||
|
||||
public Long getSpecialtyId() {
|
||||
return specialtyId;
|
||||
}
|
||||
|
||||
public void setSpecialtyId(Long specialtyId) {
|
||||
this.specialtyId = specialtyId;
|
||||
}
|
||||
|
||||
public Long getSpecialtyProfileId() {
|
||||
return specialtyProfileId;
|
||||
}
|
||||
|
||||
public void setSpecialtyProfileId(Long specialtyProfileId) {
|
||||
this.specialtyProfileId = specialtyProfileId;
|
||||
}
|
||||
|
||||
public Long getEffectiveSpecialtyId() {
|
||||
return specialtyId != null ? specialtyId : specialityCode;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.magistr.app.dto;
|
||||
|
||||
public record GroupCalendarAssignmentDto(
|
||||
Long id,
|
||||
Long groupId,
|
||||
String groupName,
|
||||
Long academicYearId,
|
||||
String academicYearTitle,
|
||||
Long calendarId,
|
||||
String calendarTitle
|
||||
) {
|
||||
}
|
||||
@@ -14,21 +14,26 @@ public class GroupResponse {
|
||||
private Integer yearStartStudy;
|
||||
private Integer course;
|
||||
private Integer semester;
|
||||
private Long specialityCode;
|
||||
private Long specialtyId;
|
||||
private String specialtyCode;
|
||||
private String specialtyName;
|
||||
private Long specialtyProfileId;
|
||||
private String specialtyProfileName;
|
||||
|
||||
public GroupResponse(Long id, String name, Long groupSize, Long educationFormId, String educationFormName, Long departmentId, Integer course, Integer semester, Long specialityCode) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.groupSize = groupSize;
|
||||
this.educationFormId = educationFormId;
|
||||
this.educationFormName = educationFormName;
|
||||
this.departmentId = departmentId;
|
||||
this.course = course;
|
||||
this.semester = semester;
|
||||
this.specialityCode = specialityCode;
|
||||
}
|
||||
|
||||
public GroupResponse(Long id, String name, Long groupSize, Long educationFormId, String educationFormName, Long departmentId, Integer yearStartStudy, Long specialityCode) {
|
||||
public GroupResponse(Long id,
|
||||
String name,
|
||||
Long groupSize,
|
||||
Long educationFormId,
|
||||
String educationFormName,
|
||||
Long departmentId,
|
||||
Integer yearStartStudy,
|
||||
Integer course,
|
||||
Integer semester,
|
||||
Long specialtyId,
|
||||
String specialtyCode,
|
||||
String specialtyName,
|
||||
Long specialtyProfileId,
|
||||
String specialtyProfileName) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.groupSize = groupSize;
|
||||
@@ -36,7 +41,13 @@ public class GroupResponse {
|
||||
this.educationFormName = educationFormName;
|
||||
this.departmentId = departmentId;
|
||||
this.yearStartStudy = yearStartStudy;
|
||||
this.specialityCode = specialityCode;
|
||||
this.course = course;
|
||||
this.semester = semester;
|
||||
this.specialtyId = specialtyId;
|
||||
this.specialtyCode = specialtyCode;
|
||||
this.specialtyName = specialtyName;
|
||||
this.specialtyProfileId = specialtyProfileId;
|
||||
this.specialtyProfileName = specialtyProfileName;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
@@ -76,6 +87,26 @@ public class GroupResponse {
|
||||
}
|
||||
|
||||
public Long getSpecialityCode() {
|
||||
return specialityCode;
|
||||
return specialtyId;
|
||||
}
|
||||
|
||||
public Long getSpecialtyId() {
|
||||
return specialtyId;
|
||||
}
|
||||
|
||||
public String getSpecialtyCode() {
|
||||
return specialtyCode;
|
||||
}
|
||||
|
||||
public String getSpecialtyName() {
|
||||
return specialtyName;
|
||||
}
|
||||
|
||||
public Long getSpecialtyProfileId() {
|
||||
return specialtyProfileId;
|
||||
}
|
||||
|
||||
public String getSpecialtyProfileName() {
|
||||
return specialtyProfileName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
package com.magistr.app.dto;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
public record HolidayDto(
|
||||
Long id,
|
||||
LocalDate date,
|
||||
Long academicYearId,
|
||||
String academicYearTitle,
|
||||
String description
|
||||
) {
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.magistr.app.dto;
|
||||
|
||||
import com.magistr.app.model.AcademicActivityType;
|
||||
import com.magistr.app.model.ScheduleParity;
|
||||
|
||||
import java.time.LocalDate;
|
||||
@@ -31,7 +30,7 @@ public record RenderedLessonDto(
|
||||
Long subgroupId,
|
||||
List<Long> groupIds,
|
||||
List<String> groupNames,
|
||||
AcademicActivityType activityType,
|
||||
String activityType,
|
||||
Integer totalAcademicHours,
|
||||
Integer consumedAcademicHoursBeforeLesson,
|
||||
Integer remainingAcademicHoursAfterLesson
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.magistr.app.dto;
|
||||
|
||||
public record SpecialtyProfileDto(
|
||||
Long id,
|
||||
Long specialtyId,
|
||||
String specialtyCode,
|
||||
String specialtyName,
|
||||
String name,
|
||||
String description
|
||||
) {
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package com.magistr.app.model;
|
||||
|
||||
public enum AcademicActivityType {
|
||||
THEORY,
|
||||
EXAM,
|
||||
VACATION,
|
||||
PRACTICE
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.magistr.app.model;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
@Entity
|
||||
@Table(name = "academic_calendars")
|
||||
public class AcademicCalendar {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String title;
|
||||
|
||||
@ManyToOne(optional = false)
|
||||
@JoinColumn(name = "academic_year_id", nullable = false)
|
||||
private AcademicYear academicYear;
|
||||
|
||||
@ManyToOne(optional = false)
|
||||
@JoinColumn(name = "specialty_id", nullable = false)
|
||||
private Speciality speciality;
|
||||
|
||||
@ManyToOne(optional = false)
|
||||
@JoinColumn(name = "specialty_profile_id", nullable = false)
|
||||
private SpecialtyProfile specialtyProfile;
|
||||
|
||||
@ManyToOne(optional = false)
|
||||
@JoinColumn(name = "study_form_id", nullable = false)
|
||||
private CalendarStudyForm studyForm;
|
||||
|
||||
@Column(name = "course_count", nullable = false)
|
||||
private Integer courseCount;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public AcademicYear getAcademicYear() {
|
||||
return academicYear;
|
||||
}
|
||||
|
||||
public void setAcademicYear(AcademicYear academicYear) {
|
||||
this.academicYear = academicYear;
|
||||
}
|
||||
|
||||
public Speciality getSpeciality() {
|
||||
return speciality;
|
||||
}
|
||||
|
||||
public void setSpeciality(Speciality speciality) {
|
||||
this.speciality = speciality;
|
||||
}
|
||||
|
||||
public SpecialtyProfile getSpecialtyProfile() {
|
||||
return specialtyProfile;
|
||||
}
|
||||
|
||||
public void setSpecialtyProfile(SpecialtyProfile specialtyProfile) {
|
||||
this.specialtyProfile = specialtyProfile;
|
||||
}
|
||||
|
||||
public CalendarStudyForm getStudyForm() {
|
||||
return studyForm;
|
||||
}
|
||||
|
||||
public void setStudyForm(CalendarStudyForm studyForm) {
|
||||
this.studyForm = studyForm;
|
||||
}
|
||||
|
||||
public Integer getCourseCount() {
|
||||
return courseCount;
|
||||
}
|
||||
|
||||
public void setCourseCount(Integer courseCount) {
|
||||
this.courseCount = courseCount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.magistr.app.model;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
@Entity
|
||||
@Table(name = "academic_calendar_activity_types")
|
||||
public class AcademicCalendarActivityType {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(nullable = false, unique = true, length = 10)
|
||||
private String code;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String name;
|
||||
|
||||
@Column(name = "allow_schedule", nullable = false)
|
||||
private Boolean allowSchedule;
|
||||
|
||||
@Column(name = "color_code", nullable = false, length = 7)
|
||||
private String colorCode;
|
||||
|
||||
@Column(name = "display_order", nullable = false)
|
||||
private Integer displayOrder;
|
||||
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String description;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Boolean getAllowSchedule() {
|
||||
return allowSchedule;
|
||||
}
|
||||
|
||||
public void setAllowSchedule(Boolean allowSchedule) {
|
||||
this.allowSchedule = allowSchedule;
|
||||
}
|
||||
|
||||
public String getColorCode() {
|
||||
return colorCode;
|
||||
}
|
||||
|
||||
public void setColorCode(String colorCode) {
|
||||
this.colorCode = colorCode;
|
||||
}
|
||||
|
||||
public Integer getDisplayOrder() {
|
||||
return displayOrder;
|
||||
}
|
||||
|
||||
public void setDisplayOrder(Integer displayOrder) {
|
||||
this.displayOrder = displayOrder;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.magistr.app.model;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Entity
|
||||
@Table(name = "academic_calendar_days")
|
||||
public class AcademicCalendarDay {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@ManyToOne(optional = false)
|
||||
@JoinColumn(name = "calendar_id", nullable = false)
|
||||
private AcademicCalendar academicCalendar;
|
||||
|
||||
@Column(name = "course_number", nullable = false)
|
||||
private Integer courseNumber;
|
||||
|
||||
@Column(nullable = false)
|
||||
private LocalDate date;
|
||||
|
||||
@Column(name = "week_number", nullable = false)
|
||||
private Integer weekNumber;
|
||||
|
||||
@Column(name = "day_of_week", nullable = false)
|
||||
private Integer dayOfWeek;
|
||||
|
||||
@ManyToOne(optional = false)
|
||||
@JoinColumn(name = "activity_type_id", nullable = false)
|
||||
private AcademicCalendarActivityType activityType;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public AcademicCalendar getAcademicCalendar() {
|
||||
return academicCalendar;
|
||||
}
|
||||
|
||||
public void setAcademicCalendar(AcademicCalendar academicCalendar) {
|
||||
this.academicCalendar = academicCalendar;
|
||||
}
|
||||
|
||||
public Integer getCourseNumber() {
|
||||
return courseNumber;
|
||||
}
|
||||
|
||||
public void setCourseNumber(Integer courseNumber) {
|
||||
this.courseNumber = courseNumber;
|
||||
}
|
||||
|
||||
public LocalDate getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
public void setDate(LocalDate date) {
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
public Integer getWeekNumber() {
|
||||
return weekNumber;
|
||||
}
|
||||
|
||||
public void setWeekNumber(Integer weekNumber) {
|
||||
this.weekNumber = weekNumber;
|
||||
}
|
||||
|
||||
public Integer getDayOfWeek() {
|
||||
return dayOfWeek;
|
||||
}
|
||||
|
||||
public void setDayOfWeek(Integer dayOfWeek) {
|
||||
this.dayOfWeek = dayOfWeek;
|
||||
}
|
||||
|
||||
public AcademicCalendarActivityType getActivityType() {
|
||||
return activityType;
|
||||
}
|
||||
|
||||
public void setActivityType(AcademicCalendarActivityType activityType) {
|
||||
this.activityType = activityType;
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
package com.magistr.app.model;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
@Entity
|
||||
@Table(name = "academic_calendar_matrix")
|
||||
public class AcademicCalendarMatrix {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@ManyToOne(optional = false)
|
||||
@JoinColumn(name = "semester_id", nullable = false)
|
||||
private Semester semester;
|
||||
|
||||
@Column(name = "course_number", nullable = false)
|
||||
private Integer courseNumber;
|
||||
|
||||
@Column(name = "specialty_id", nullable = false)
|
||||
private Long specialtyId;
|
||||
|
||||
@Column(name = "week_number", nullable = false)
|
||||
private Integer weekNumber;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "activity_type", nullable = false, length = 20)
|
||||
private AcademicActivityType activityType;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Semester getSemester() {
|
||||
return semester;
|
||||
}
|
||||
|
||||
public void setSemester(Semester semester) {
|
||||
this.semester = semester;
|
||||
}
|
||||
|
||||
public Integer getCourseNumber() {
|
||||
return courseNumber;
|
||||
}
|
||||
|
||||
public void setCourseNumber(Integer courseNumber) {
|
||||
this.courseNumber = courseNumber;
|
||||
}
|
||||
|
||||
public Long getSpecialtyId() {
|
||||
return specialtyId;
|
||||
}
|
||||
|
||||
public void setSpecialtyId(Long specialtyId) {
|
||||
this.specialtyId = specialtyId;
|
||||
}
|
||||
|
||||
public Integer getWeekNumber() {
|
||||
return weekNumber;
|
||||
}
|
||||
|
||||
public void setWeekNumber(Integer weekNumber) {
|
||||
this.weekNumber = weekNumber;
|
||||
}
|
||||
|
||||
public AcademicActivityType getActivityType() {
|
||||
return activityType;
|
||||
}
|
||||
|
||||
public void setActivityType(AcademicActivityType activityType) {
|
||||
this.activityType = activityType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.magistr.app.model;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
@Entity
|
||||
@Table(name = "calendar_study_forms")
|
||||
public class CalendarStudyForm {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(nullable = false, unique = true, length = 100)
|
||||
private String name;
|
||||
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String description;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
package com.magistr.app.model;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Entity
|
||||
@Table(name = "holidays")
|
||||
public class Holiday {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(nullable = false)
|
||||
private LocalDate date;
|
||||
|
||||
@ManyToOne(optional = false)
|
||||
@JoinColumn(name = "academic_year_id", nullable = false)
|
||||
private AcademicYear academicYear;
|
||||
|
||||
@Column(length = 255)
|
||||
private String description;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public LocalDate getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
public void setDate(LocalDate date) {
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
public AcademicYear getAcademicYear() {
|
||||
return academicYear;
|
||||
}
|
||||
|
||||
public void setAcademicYear(AcademicYear academicYear) {
|
||||
this.academicYear = academicYear;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.magistr.app.model;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
@Entity
|
||||
@Table(name = "specialty_profiles")
|
||||
public class SpecialtyProfile {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@ManyToOne(optional = false)
|
||||
@JoinColumn(name = "specialty_id", nullable = false)
|
||||
private Speciality speciality;
|
||||
|
||||
@Column(nullable = false, length = 500)
|
||||
private String name;
|
||||
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String description;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Speciality getSpeciality() {
|
||||
return speciality;
|
||||
}
|
||||
|
||||
public void setSpeciality(Speciality speciality) {
|
||||
this.speciality = speciality;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ public class StudentGroup {
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(unique = true, nullable = false, length = 100)
|
||||
@Column(nullable = false, length = 100)
|
||||
private String name;
|
||||
|
||||
@Column(name = "group_size", nullable = false)
|
||||
@@ -23,8 +23,13 @@ public class StudentGroup {
|
||||
@Column(name = "department_id", nullable = false)
|
||||
private Long departmentId;
|
||||
|
||||
@Column(name="specialty_code", nullable = false)
|
||||
private Long specialityCode;
|
||||
@ManyToOne(optional = false)
|
||||
@JoinColumn(name = "specialty_id", nullable = false)
|
||||
private Speciality speciality;
|
||||
|
||||
@ManyToOne(optional = false)
|
||||
@JoinColumn(name = "specialty_profile_id", nullable = false)
|
||||
private SpecialtyProfile specialtyProfile;
|
||||
|
||||
@Column(name="year_start_study", nullable = false)
|
||||
private Integer yearStartStudy;
|
||||
@@ -72,12 +77,20 @@ public class StudentGroup {
|
||||
this.departmentId = departmentId;
|
||||
}
|
||||
|
||||
public Long getSpecialityCode() {
|
||||
return specialityCode;
|
||||
public Speciality getSpeciality() {
|
||||
return speciality;
|
||||
}
|
||||
|
||||
public void setSpecialityCode(Long specialityCode) {
|
||||
this.specialityCode = specialityCode;
|
||||
public void setSpeciality(Speciality speciality) {
|
||||
this.speciality = speciality;
|
||||
}
|
||||
|
||||
public SpecialtyProfile getSpecialtyProfile() {
|
||||
return specialtyProfile;
|
||||
}
|
||||
|
||||
public void setSpecialtyProfile(SpecialtyProfile specialtyProfile) {
|
||||
this.specialtyProfile = specialtyProfile;
|
||||
}
|
||||
|
||||
public Integer getYearStartStudy() {
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.magistr.app.model;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
@Entity
|
||||
@Table(name = "student_group_calendar_assignments")
|
||||
public class StudentGroupCalendarAssignment {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@ManyToOne(optional = false)
|
||||
@JoinColumn(name = "group_id", nullable = false)
|
||||
private StudentGroup studentGroup;
|
||||
|
||||
@ManyToOne(optional = false)
|
||||
@JoinColumn(name = "academic_year_id", nullable = false)
|
||||
private AcademicYear academicYear;
|
||||
|
||||
@ManyToOne(optional = false)
|
||||
@JoinColumn(name = "calendar_id", nullable = false)
|
||||
private AcademicCalendar academicCalendar;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public StudentGroup getStudentGroup() {
|
||||
return studentGroup;
|
||||
}
|
||||
|
||||
public void setStudentGroup(StudentGroup studentGroup) {
|
||||
this.studentGroup = studentGroup;
|
||||
}
|
||||
|
||||
public AcademicYear getAcademicYear() {
|
||||
return academicYear;
|
||||
}
|
||||
|
||||
public void setAcademicYear(AcademicYear academicYear) {
|
||||
this.academicYear = academicYear;
|
||||
}
|
||||
|
||||
public AcademicCalendar getAcademicCalendar() {
|
||||
return academicCalendar;
|
||||
}
|
||||
|
||||
public void setAcademicCalendar(AcademicCalendar academicCalendar) {
|
||||
this.academicCalendar = academicCalendar;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.magistr.app.repository;
|
||||
|
||||
import com.magistr.app.model.AcademicCalendarActivityType;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface AcademicCalendarActivityTypeRepository extends JpaRepository<AcademicCalendarActivityType, Long> {
|
||||
|
||||
List<AcademicCalendarActivityType> findAllByOrderByDisplayOrderAscCodeAsc();
|
||||
|
||||
Optional<AcademicCalendarActivityType> findByCode(String code);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.magistr.app.repository;
|
||||
|
||||
import com.magistr.app.model.AcademicCalendarDay;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface AcademicCalendarDayRepository extends JpaRepository<AcademicCalendarDay, Long> {
|
||||
|
||||
@Query("""
|
||||
select day
|
||||
from AcademicCalendarDay day
|
||||
join fetch day.activityType
|
||||
where day.academicCalendar.id = :calendarId
|
||||
order by day.courseNumber asc, day.date asc
|
||||
""")
|
||||
List<AcademicCalendarDay> findByCalendarIdWithActivity(@Param("calendarId") Long calendarId);
|
||||
|
||||
@Query("""
|
||||
select day
|
||||
from AcademicCalendarDay day
|
||||
join fetch day.activityType
|
||||
where day.academicCalendar.id = :calendarId
|
||||
and day.courseNumber = :courseNumber
|
||||
and day.date = :date
|
||||
""")
|
||||
Optional<AcademicCalendarDay> findByCalendarIdAndCourseNumberAndDate(
|
||||
@Param("calendarId") Long calendarId,
|
||||
@Param("courseNumber") Integer courseNumber,
|
||||
@Param("date") LocalDate date
|
||||
);
|
||||
|
||||
boolean existsByActivityTypeId(Long activityTypeId);
|
||||
|
||||
@Modifying
|
||||
@Query("delete from AcademicCalendarDay day where day.academicCalendar.id = :calendarId")
|
||||
void deleteByCalendarId(@Param("calendarId") Long calendarId);
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package com.magistr.app.repository;
|
||||
|
||||
import com.magistr.app.model.AcademicCalendarMatrix;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface AcademicCalendarMatrixRepository extends JpaRepository<AcademicCalendarMatrix, Long> {
|
||||
|
||||
Optional<AcademicCalendarMatrix> findBySemesterIdAndCourseNumberAndSpecialtyIdAndWeekNumber(
|
||||
Long semesterId,
|
||||
Integer courseNumber,
|
||||
Long specialtyId,
|
||||
Integer weekNumber
|
||||
);
|
||||
|
||||
List<AcademicCalendarMatrix> findBySemesterIdAndCourseNumberAndSpecialtyIdOrderByWeekNumberAsc(
|
||||
Long semesterId,
|
||||
Integer courseNumber,
|
||||
Long specialtyId
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.magistr.app.repository;
|
||||
|
||||
import com.magistr.app.model.AcademicCalendar;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface AcademicCalendarRepository extends JpaRepository<AcademicCalendar, Long> {
|
||||
|
||||
@Query("""
|
||||
select calendar
|
||||
from AcademicCalendar calendar
|
||||
join fetch calendar.academicYear
|
||||
join fetch calendar.speciality
|
||||
join fetch calendar.specialtyProfile
|
||||
join fetch calendar.studyForm
|
||||
where (:academicYearId is null or calendar.academicYear.id = :academicYearId)
|
||||
and (:specialtyId is null or calendar.speciality.id = :specialtyId)
|
||||
and (:profileId is null or calendar.specialtyProfile.id = :profileId)
|
||||
order by calendar.academicYear.startDate desc, calendar.title asc
|
||||
""")
|
||||
List<AcademicCalendar> findAllWithDetails(
|
||||
@Param("academicYearId") Long academicYearId,
|
||||
@Param("specialtyId") Long specialtyId,
|
||||
@Param("profileId") Long profileId
|
||||
);
|
||||
|
||||
@Query("""
|
||||
select calendar
|
||||
from AcademicCalendar calendar
|
||||
join fetch calendar.academicYear
|
||||
join fetch calendar.speciality
|
||||
join fetch calendar.specialtyProfile
|
||||
join fetch calendar.studyForm
|
||||
where calendar.id = :id
|
||||
""")
|
||||
Optional<AcademicCalendar> findByIdWithDetails(@Param("id") Long id);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.magistr.app.repository;
|
||||
|
||||
import com.magistr.app.model.CalendarStudyForm;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface CalendarStudyFormRepository extends JpaRepository<CalendarStudyForm, Long> {
|
||||
|
||||
List<CalendarStudyForm> findAllByOrderByNameAsc();
|
||||
}
|
||||
@@ -4,12 +4,9 @@ import com.magistr.app.model.StudentGroup;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface GroupRepository extends JpaRepository<StudentGroup, Long> {
|
||||
|
||||
Optional<StudentGroup> findByName(String name);
|
||||
|
||||
List<StudentGroup> findByEducationFormId(Long educationFormId);
|
||||
|
||||
List<StudentGroup> findByDepartmentId(Long departmentId);
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.magistr.app.repository;
|
||||
|
||||
import com.magistr.app.model.Holiday;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
public interface HolidayRepository extends JpaRepository<Holiday, Long> {
|
||||
|
||||
List<Holiday> findByAcademicYearIdOrderByDateAsc(Long academicYearId);
|
||||
|
||||
List<Holiday> findByAcademicYearIdAndDateBetween(Long academicYearId, LocalDate startDate, LocalDate endDate);
|
||||
|
||||
boolean existsByAcademicYearIdAndDate(Long academicYearId, LocalDate date);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.magistr.app.repository;
|
||||
|
||||
import com.magistr.app.model.SpecialtyProfile;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface SpecialtyProfileRepository extends JpaRepository<SpecialtyProfile, Long> {
|
||||
|
||||
List<SpecialtyProfile> findBySpecialityIdOrderByNameAsc(Long specialityId);
|
||||
|
||||
Optional<SpecialtyProfile> findBySpecialityIdAndName(Long specialityId, String name);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.magistr.app.repository;
|
||||
|
||||
import com.magistr.app.model.StudentGroupCalendarAssignment;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface StudentGroupCalendarAssignmentRepository extends JpaRepository<StudentGroupCalendarAssignment, Long> {
|
||||
|
||||
@Query("""
|
||||
select assignment
|
||||
from StudentGroupCalendarAssignment assignment
|
||||
join fetch assignment.studentGroup
|
||||
join fetch assignment.academicYear
|
||||
join fetch assignment.academicCalendar calendar
|
||||
join fetch calendar.speciality
|
||||
join fetch calendar.specialtyProfile
|
||||
join fetch calendar.studyForm
|
||||
where assignment.studentGroup.id = :groupId
|
||||
order by assignment.academicYear.startDate desc
|
||||
""")
|
||||
List<StudentGroupCalendarAssignment> findByGroupIdWithDetails(@Param("groupId") Long groupId);
|
||||
|
||||
@Query("""
|
||||
select assignment
|
||||
from StudentGroupCalendarAssignment assignment
|
||||
join fetch assignment.academicYear
|
||||
join fetch assignment.academicCalendar calendar
|
||||
join fetch calendar.speciality
|
||||
join fetch calendar.specialtyProfile
|
||||
join fetch calendar.studyForm
|
||||
where assignment.studentGroup.id = :groupId
|
||||
and assignment.academicYear.id = :academicYearId
|
||||
""")
|
||||
Optional<StudentGroupCalendarAssignment> findByGroupIdAndAcademicYearIdWithDetails(
|
||||
@Param("groupId") Long groupId,
|
||||
@Param("academicYearId") Long academicYearId
|
||||
);
|
||||
|
||||
@Query("""
|
||||
select assignment
|
||||
from StudentGroupCalendarAssignment assignment
|
||||
join fetch assignment.academicCalendar
|
||||
where assignment.studentGroup.id = :groupId
|
||||
and assignment.academicYear.id = :academicYearId
|
||||
""")
|
||||
Optional<StudentGroupCalendarAssignment> findForSchedule(
|
||||
@Param("groupId") Long groupId,
|
||||
@Param("academicYearId") Long academicYearId
|
||||
);
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package com.magistr.app.service;
|
||||
|
||||
import com.magistr.app.model.*;
|
||||
import com.magistr.app.repository.AcademicCalendarMatrixRepository;
|
||||
import com.magistr.app.repository.HolidayRepository;
|
||||
import com.magistr.app.repository.AcademicCalendarDayRepository;
|
||||
import com.magistr.app.repository.SemesterRepository;
|
||||
import com.magistr.app.repository.StudentGroupCalendarAssignmentRepository;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@@ -15,17 +15,17 @@ import java.util.Optional;
|
||||
public class AcademicDateService {
|
||||
|
||||
private final SemesterRepository semesterRepository;
|
||||
private final HolidayRepository holidayRepository;
|
||||
private final AcademicCalendarMatrixRepository calendarMatrixRepository;
|
||||
private final AcademicCalendarDayRepository calendarDayRepository;
|
||||
private final StudentGroupCalendarAssignmentRepository assignmentRepository;
|
||||
private final boolean evenWeekIsUpper;
|
||||
|
||||
public AcademicDateService(SemesterRepository semesterRepository,
|
||||
HolidayRepository holidayRepository,
|
||||
AcademicCalendarMatrixRepository calendarMatrixRepository,
|
||||
AcademicCalendarDayRepository calendarDayRepository,
|
||||
StudentGroupCalendarAssignmentRepository assignmentRepository,
|
||||
@Value("${app.schedule.even-week-is-upper:false}") boolean evenWeekIsUpper) {
|
||||
this.semesterRepository = semesterRepository;
|
||||
this.holidayRepository = holidayRepository;
|
||||
this.calendarMatrixRepository = calendarMatrixRepository;
|
||||
this.calendarDayRepository = calendarDayRepository;
|
||||
this.assignmentRepository = assignmentRepository;
|
||||
this.evenWeekIsUpper = evenWeekIsUpper;
|
||||
}
|
||||
|
||||
@@ -50,38 +50,40 @@ public class AcademicDateService {
|
||||
return evenWeek ? ScheduleParity.EVEN : ScheduleParity.ODD;
|
||||
}
|
||||
|
||||
public boolean isHoliday(Semester semester, LocalDate date) {
|
||||
if (semester == null || semester.getAcademicYear() == null) {
|
||||
return false;
|
||||
}
|
||||
return holidayRepository.existsByAcademicYearIdAndDate(semester.getAcademicYear().getId(), date);
|
||||
}
|
||||
|
||||
public int getCourseNumber(StudentGroup group, LocalDate date) {
|
||||
int academicYearStart = date.getMonthValue() >= 9 ? date.getYear() : date.getYear() - 1;
|
||||
return academicYearStart - group.getYearStartStudy() + 1;
|
||||
}
|
||||
|
||||
public AcademicActivityType getActivityType(StudentGroup group, Semester semester, LocalDate date) {
|
||||
int weekNumber = getWeekNumber(semester, date);
|
||||
int courseNumber = getCourseNumber(group, date);
|
||||
if (weekNumber < 1 || courseNumber < 1) {
|
||||
return AcademicActivityType.VACATION;
|
||||
public Optional<AcademicCalendarActivityType> getActivityType(StudentGroup group, Semester semester, LocalDate date) {
|
||||
if (group == null || semester == null || semester.getAcademicYear() == null || date == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return calendarMatrixRepository
|
||||
.findBySemesterIdAndCourseNumberAndSpecialtyIdAndWeekNumber(
|
||||
semester.getId(),
|
||||
int courseNumber = getCourseNumber(group, date);
|
||||
if (courseNumber < 1) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return assignmentRepository.findForSchedule(group.getId(), semester.getAcademicYear().getId())
|
||||
.flatMap(assignment -> calendarDayRepository.findByCalendarIdAndCourseNumberAndDate(
|
||||
assignment.getAcademicCalendar().getId(),
|
||||
courseNumber,
|
||||
group.getSpecialityCode(),
|
||||
weekNumber
|
||||
)
|
||||
.map(AcademicCalendarMatrix::getActivityType)
|
||||
.orElse(AcademicActivityType.THEORY);
|
||||
date
|
||||
))
|
||||
.map(AcademicCalendarDay::getActivityType);
|
||||
}
|
||||
|
||||
public boolean isTheoryDay(StudentGroup group, Semester semester, LocalDate date) {
|
||||
return getActivityType(group, semester, date) == AcademicActivityType.THEORY
|
||||
&& !isHoliday(semester, date);
|
||||
return getActivityType(group, semester, date)
|
||||
.map(AcademicCalendarActivityType::getAllowSchedule)
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
public boolean hasCalendarAssignment(StudentGroup group, Semester semester) {
|
||||
if (group == null || semester == null || semester.getAcademicYear() == null) {
|
||||
return false;
|
||||
}
|
||||
return assignmentRepository.findForSchedule(group.getId(), semester.getAcademicYear().getId()).isPresent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import com.magistr.app.model.*;
|
||||
import com.magistr.app.repository.GroupRepository;
|
||||
import com.magistr.app.repository.ScheduleRuleRepository;
|
||||
import com.magistr.app.repository.UserRepository;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.DayOfWeek;
|
||||
@@ -17,6 +19,7 @@ import java.util.stream.Collectors;
|
||||
@Service
|
||||
public class ScheduleGeneratorService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ScheduleGeneratorService.class);
|
||||
private static final int ACADEMIC_HOURS_PER_SLOT = 2;
|
||||
private static final long MAX_RANGE_DAYS = 120;
|
||||
|
||||
@@ -43,6 +46,7 @@ public class ScheduleGeneratorService {
|
||||
|
||||
Map<Long, List<ScheduleRule>> rulesBySemester = new HashMap<>();
|
||||
List<RenderedLessonDto> result = new ArrayList<>();
|
||||
Set<Long> warnedAcademicYears = new HashSet<>();
|
||||
|
||||
for (LocalDate date = startDate; !date.isAfter(endDate); date = date.plusDays(1)) {
|
||||
Optional<Semester> semesterOpt = academicDateService.findSemester(date);
|
||||
@@ -51,6 +55,11 @@ public class ScheduleGeneratorService {
|
||||
}
|
||||
|
||||
Semester semester = semesterOpt.get();
|
||||
if (!academicDateService.hasCalendarAssignment(group, semester)
|
||||
&& warnedAcademicYears.add(semester.getAcademicYear().getId())) {
|
||||
logger.info("Для группы '{}' не назначен календарный учебный график на учебный год {}",
|
||||
group.getName(), semester.getAcademicYear().getTitle());
|
||||
}
|
||||
if (!academicDateService.isTheoryDay(group, semester, date)) {
|
||||
continue;
|
||||
}
|
||||
@@ -148,10 +157,6 @@ public class ScheduleGeneratorService {
|
||||
}
|
||||
|
||||
Semester semester = semesterOpt.get();
|
||||
if (academicDateService.isHoliday(semester, date)) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<StudentGroup> eligibleGroups = eligibleGroups(rule, targetGroup, semester, date);
|
||||
if (eligibleGroups.isEmpty()) {
|
||||
return;
|
||||
@@ -216,9 +221,6 @@ public class ScheduleGeneratorService {
|
||||
if (date.isBefore(semester.getStartDate()) || date.isAfter(semester.getEndDate())) {
|
||||
continue;
|
||||
}
|
||||
if (academicDateService.isHoliday(semester, date)) {
|
||||
continue;
|
||||
}
|
||||
if (eligibleGroups(rule, targetGroup, semester, date).isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
@@ -274,7 +276,7 @@ public class ScheduleGeneratorService {
|
||||
slot.getSubgroupId(),
|
||||
sortedGroups.stream().map(StudentGroup::getId).toList(),
|
||||
sortedGroups.stream().map(StudentGroup::getName).collect(Collectors.toList()),
|
||||
AcademicActivityType.THEORY,
|
||||
"Т",
|
||||
rule.getTotalAcademicHours(),
|
||||
consumedBeforeLesson,
|
||||
remainingAfterLesson
|
||||
|
||||
@@ -20,13 +20,28 @@ INSERT INTO departments (name, code) VALUES
|
||||
CREATE TABLE IF NOT EXISTS specialties (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
specialty_code VARCHAR(255) NOT NULL
|
||||
specialty_code VARCHAR(255) UNIQUE NOT NULL
|
||||
);
|
||||
|
||||
INSERT INTO specialties (name, specialty_code) VALUES
|
||||
('Информационная безопасность', '10.03.01'),
|
||||
('Информатика и вычислительная техника', '09.03.01'),
|
||||
('Программная инженерия', '09.03.04');
|
||||
('Программная инженерия', '09.03.04')
|
||||
ON CONFLICT (specialty_code) DO NOTHING;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS specialty_profiles (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
specialty_id BIGINT NOT NULL REFERENCES specialties(id) ON DELETE CASCADE,
|
||||
name VARCHAR(500) NOT NULL,
|
||||
description TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT uq_specialty_profiles_name UNIQUE (specialty_id, name)
|
||||
);
|
||||
|
||||
INSERT INTO specialty_profiles (specialty_id, name)
|
||||
SELECT id, 'Без профиля'
|
||||
FROM specialties
|
||||
ON CONFLICT (specialty_id, name) DO NOTHING;
|
||||
|
||||
-- ==========================================
|
||||
-- Пользователи и роли
|
||||
@@ -70,20 +85,28 @@ ON CONFLICT (name) DO NOTHING;
|
||||
-- ==========================================
|
||||
CREATE TABLE IF NOT EXISTS student_groups (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
name VARCHAR(100) UNIQUE NOT NULL,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
group_size BIGINT NOT NULL,
|
||||
education_form_id BIGINT NOT NULL REFERENCES education_forms(id),
|
||||
department_id BIGINT NOT NULL REFERENCES departments(id),
|
||||
specialty_code INT NOT NULL REFERENCES specialties(id),
|
||||
specialty_id BIGINT NOT NULL REFERENCES specialties(id),
|
||||
specialty_profile_id BIGINT NOT NULL REFERENCES specialty_profiles(id),
|
||||
year_start_study BIGINT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Тестовая базовая группа для работы
|
||||
INSERT INTO student_groups (name, group_size, education_form_id, department_id, specialty_code, year_start_study)
|
||||
VALUES ('ИВТ-21-1', 25, 1, 1, 2, 2025),
|
||||
('ИБ-41м', 15, 2, 1, 1, 2024)
|
||||
ON CONFLICT (name) DO NOTHING;
|
||||
INSERT INTO student_groups (name, group_size, education_form_id, department_id, specialty_id, specialty_profile_id, year_start_study)
|
||||
SELECT data.name, data.group_size, ef.id, dept.id, specialty.id, profile.id, data.year_start_study
|
||||
FROM (VALUES
|
||||
('ИВТ-21-1', 25::BIGINT, 'Бакалавриат', 'Кафедра ИБ', '09.03.01', 2025::BIGINT),
|
||||
('ИБ-41м', 15::BIGINT, 'Магистратура', 'Кафедра ИБ', '10.03.01', 2024::BIGINT)
|
||||
) AS data(name, group_size, education_form_name, department_name, specialty_code, year_start_study)
|
||||
JOIN education_forms ef ON ef.name = data.education_form_name
|
||||
JOIN departments dept ON dept.name = data.department_name
|
||||
JOIN specialties specialty ON specialty.specialty_code = data.specialty_code
|
||||
JOIN specialty_profiles profile ON profile.specialty_id = specialty.id AND profile.name = 'Без профиля'
|
||||
WHERE NOT EXISTS (SELECT 1 FROM student_groups existing WHERE existing.name = data.name);
|
||||
|
||||
-- ==========================================
|
||||
-- Подгруппы (например: "ИВТ-21-1 Подгруппа 1")
|
||||
@@ -272,45 +295,131 @@ ON CONFLICT (academic_year_id, semester_type) DO NOTHING;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_semesters_dates ON semesters(start_date, end_date);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS holidays (
|
||||
CREATE TABLE IF NOT EXISTS calendar_study_forms (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
date DATE NOT NULL,
|
||||
name VARCHAR(100) UNIQUE NOT NULL,
|
||||
description TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
INSERT INTO calendar_study_forms (name, description) VALUES
|
||||
('очная', 'Очная форма обучения')
|
||||
ON CONFLICT (name) DO NOTHING;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS academic_calendar_activity_types (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
code VARCHAR(10) UNIQUE NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
allow_schedule BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
color_code VARCHAR(7) NOT NULL DEFAULT '#64748b',
|
||||
display_order INT NOT NULL DEFAULT 100,
|
||||
description TEXT
|
||||
);
|
||||
|
||||
INSERT INTO academic_calendar_activity_types (code, name, allow_schedule, color_code, display_order, description) VALUES
|
||||
('Т', 'Теоретическое обучение', TRUE, '#10b981', 10, 'Обычные занятия динамического расписания разрешены'),
|
||||
('Э', 'Экзаменационная сессия', FALSE, '#f59e0b', 20, 'Обычные занятия не генерируются'),
|
||||
('К', 'Каникулы', FALSE, '#38bdf8', 30, 'Обычные занятия не генерируются'),
|
||||
('У', 'Учебная практика', FALSE, '#a78bfa', 40, 'Обычные занятия не генерируются'),
|
||||
('П', 'Производственная практика', FALSE, '#fb7185', 50, 'Обычные занятия не генерируются'),
|
||||
('Пд', 'Преддипломная практика', FALSE, '#f97316', 60, 'Обычные занятия не генерируются'),
|
||||
('Н', 'Научно-исследовательская работа', FALSE, '#22c55e', 70, 'Обычные занятия не генерируются'),
|
||||
('Г', 'Подготовка к сдаче и сдача государственного экзамена', FALSE, '#ef4444', 80, 'Обычные занятия не генерируются'),
|
||||
('Д', 'Подготовка к защите и защита выпускной квалификационной работы', FALSE, '#dc2626', 90, 'Обычные занятия не генерируются'),
|
||||
('ПА', 'Повторная промежуточная аттестация', FALSE, '#eab308', 100, 'Обычные занятия не генерируются'),
|
||||
('С', 'Симуляционный курс', FALSE, '#06b6d4', 110, 'Обычные занятия не генерируются'),
|
||||
('*', 'Нерабочий праздничный день', FALSE, '#64748b', 120, 'День исключается из генерации расписания'),
|
||||
('=', 'День вне учебного года', FALSE, '#475569', 130, 'День исключается из генерации расписания')
|
||||
ON CONFLICT (code) DO NOTHING;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS academic_calendars (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
academic_year_id BIGINT NOT NULL REFERENCES academic_years(id) ON DELETE CASCADE,
|
||||
description VARCHAR(255),
|
||||
CONSTRAINT uq_holidays_year_date UNIQUE (academic_year_id, date)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_holidays_date ON holidays(date);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS academic_calendar_matrix (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
semester_id BIGINT NOT NULL REFERENCES semesters(id) ON DELETE CASCADE,
|
||||
course_number INT NOT NULL,
|
||||
specialty_id BIGINT NOT NULL REFERENCES specialties(id),
|
||||
week_number INT NOT NULL,
|
||||
activity_type VARCHAR(20) NOT NULL,
|
||||
CONSTRAINT chk_calendar_course_positive CHECK (course_number > 0),
|
||||
CONSTRAINT chk_calendar_week_positive CHECK (week_number > 0),
|
||||
CONSTRAINT chk_calendar_activity_type CHECK (activity_type IN ('THEORY', 'EXAM', 'VACATION', 'PRACTICE')),
|
||||
CONSTRAINT uq_calendar_matrix UNIQUE (semester_id, course_number, specialty_id, week_number)
|
||||
specialty_profile_id BIGINT NOT NULL REFERENCES specialty_profiles(id),
|
||||
study_form_id BIGINT NOT NULL REFERENCES calendar_study_forms(id),
|
||||
course_count INT NOT NULL DEFAULT 4,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT chk_academic_calendars_course_count CHECK (course_count > 0),
|
||||
CONSTRAINT uq_academic_calendar_title UNIQUE (academic_year_id, specialty_profile_id, study_form_id, title)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_calendar_lookup
|
||||
ON academic_calendar_matrix(semester_id, course_number, specialty_id, week_number);
|
||||
CREATE INDEX IF NOT EXISTS idx_academic_calendars_context
|
||||
ON academic_calendars(academic_year_id, specialty_id, specialty_profile_id, study_form_id);
|
||||
|
||||
INSERT INTO academic_calendar_matrix (semester_id, course_number, specialty_id, week_number, activity_type)
|
||||
INSERT INTO academic_calendars (title, academic_year_id, specialty_id, specialty_profile_id, study_form_id, course_count)
|
||||
SELECT
|
||||
s.id,
|
||||
SPLIT_PART(ay.title, '-', 1)::INT - sg.year_start_study + 1 AS course_number,
|
||||
sg.specialty_code AS specialty_id,
|
||||
weeks.week_number,
|
||||
'THEORY'
|
||||
FROM semesters s
|
||||
JOIN academic_years ay ON ay.id = s.academic_year_id
|
||||
CROSS JOIN student_groups sg
|
||||
CROSS JOIN GENERATE_SERIES(1, 22) AS weeks(week_number)
|
||||
WHERE SPLIT_PART(ay.title, '-', 1)::INT - sg.year_start_study + 1 > 0
|
||||
ON CONFLICT (semester_id, course_number, specialty_id, week_number) DO NOTHING;
|
||||
specialty.specialty_code || ' ' || profile.name || ' ' || study_form.name || ' ' || ay.title,
|
||||
ay.id,
|
||||
specialty.id,
|
||||
profile.id,
|
||||
study_form.id,
|
||||
4
|
||||
FROM academic_years ay
|
||||
CROSS JOIN calendar_study_forms study_form
|
||||
JOIN specialties specialty ON specialty.specialty_code IN ('09.03.01', '10.03.01')
|
||||
JOIN specialty_profiles profile ON profile.specialty_id = specialty.id AND profile.name = 'Без профиля'
|
||||
WHERE ay.title = '2025-2026'
|
||||
ON CONFLICT (academic_year_id, specialty_profile_id, study_form_id, title) DO NOTHING;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS academic_calendar_days (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
calendar_id BIGINT NOT NULL REFERENCES academic_calendars(id) ON DELETE CASCADE,
|
||||
course_number INT NOT NULL,
|
||||
date DATE NOT NULL,
|
||||
week_number INT NOT NULL,
|
||||
day_of_week INT NOT NULL,
|
||||
activity_type_id BIGINT NOT NULL REFERENCES academic_calendar_activity_types(id),
|
||||
CONSTRAINT chk_calendar_days_course_positive CHECK (course_number > 0),
|
||||
CONSTRAINT chk_calendar_days_week_positive CHECK (week_number > 0),
|
||||
CONSTRAINT chk_calendar_days_day CHECK (day_of_week BETWEEN 1 AND 7),
|
||||
CONSTRAINT uq_calendar_days UNIQUE (calendar_id, course_number, date)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_calendar_days_lookup
|
||||
ON academic_calendar_days(calendar_id, course_number, date);
|
||||
|
||||
INSERT INTO academic_calendar_days (calendar_id, course_number, date, week_number, day_of_week, activity_type_id)
|
||||
SELECT
|
||||
calendar.id,
|
||||
course.course_number,
|
||||
days.date::DATE,
|
||||
((days.date::DATE - ay.start_date) / 7) + 1,
|
||||
EXTRACT(ISODOW FROM days.date::DATE)::INT,
|
||||
activity.id
|
||||
FROM academic_calendars calendar
|
||||
JOIN academic_years ay ON ay.id = calendar.academic_year_id
|
||||
JOIN academic_calendar_activity_types activity ON activity.code = 'Т'
|
||||
CROSS JOIN GENERATE_SERIES(1, calendar.course_count) AS course(course_number)
|
||||
CROSS JOIN GENERATE_SERIES(ay.start_date, ay.end_date, INTERVAL '1 day') AS days(date)
|
||||
ON CONFLICT (calendar_id, course_number, date) DO NOTHING;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS student_group_calendar_assignments (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
group_id BIGINT NOT NULL REFERENCES student_groups(id) ON DELETE CASCADE,
|
||||
academic_year_id BIGINT NOT NULL REFERENCES academic_years(id) ON DELETE CASCADE,
|
||||
calendar_id BIGINT NOT NULL REFERENCES academic_calendars(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT uq_group_calendar_year UNIQUE (group_id, academic_year_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_group_calendar_assignments_lookup
|
||||
ON student_group_calendar_assignments(group_id, academic_year_id);
|
||||
|
||||
INSERT INTO student_group_calendar_assignments (group_id, academic_year_id, calendar_id)
|
||||
SELECT
|
||||
sg.id,
|
||||
ay.id,
|
||||
calendar.id
|
||||
FROM student_groups sg
|
||||
JOIN academic_years ay ON ay.title = '2025-2026'
|
||||
JOIN academic_calendars calendar
|
||||
ON calendar.academic_year_id = ay.id
|
||||
AND calendar.specialty_profile_id = sg.specialty_profile_id
|
||||
WHERE sg.name IN ('ИВТ-21-1', 'ИБ-41м')
|
||||
ON CONFLICT (group_id, academic_year_id) DO NOTHING;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS schedule_rules (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
@@ -445,17 +554,26 @@ CREATE TRIGGER update_users_updated_at
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_academic_calendars_updated_at
|
||||
BEFORE UPDATE ON academic_calendars
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
-- ==========================================
|
||||
-- Комментарии к таблицам и полям (для документации)
|
||||
-- ==========================================
|
||||
COMMENT ON TABLE users IS 'Пользователи системы (студенты, преподаватели, администраторы)';
|
||||
COMMENT ON TABLE departments IS 'Кафедры';
|
||||
COMMENT ON TABLE specialties IS 'Специальности';
|
||||
COMMENT ON TABLE specialty_profiles IS 'Профили обучения внутри специальности';
|
||||
COMMENT ON TABLE time_slots IS 'Настраиваемая сетка временных слотов занятий';
|
||||
COMMENT ON TABLE academic_years IS 'Учебные годы';
|
||||
COMMENT ON TABLE semesters IS 'Семестры учебного года';
|
||||
COMMENT ON TABLE holidays IS 'Праздники и неучебные даты';
|
||||
COMMENT ON TABLE academic_calendar_matrix IS 'Матрица учебного графика по курсу, специальности и неделе';
|
||||
COMMENT ON TABLE calendar_study_forms IS 'Формы обучения календарного учебного графика';
|
||||
COMMENT ON TABLE academic_calendar_activity_types IS 'Коды активностей календарного учебного графика';
|
||||
COMMENT ON TABLE academic_calendars IS 'Календарные учебные графики по профилю, форме обучения и учебному году';
|
||||
COMMENT ON TABLE academic_calendar_days IS 'Дневные ячейки календарного учебного графика';
|
||||
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 'Слоты проведения занятий внутри правила расписания';
|
||||
@@ -489,7 +607,8 @@ COMMENT ON COLUMN student_groups.name IS 'Название группы';
|
||||
COMMENT ON COLUMN student_groups.group_size IS 'Количество студентов';
|
||||
COMMENT ON COLUMN student_groups.education_form_id IS 'ID формы обучения, к которой относится группа';
|
||||
COMMENT ON COLUMN student_groups.department_id IS 'ID кафедры';
|
||||
COMMENT ON COLUMN student_groups.specialty_code IS 'ID специальности';
|
||||
COMMENT ON COLUMN student_groups.specialty_id IS 'ID специальности';
|
||||
COMMENT ON COLUMN student_groups.specialty_profile_id IS 'ID профиля обучения';
|
||||
COMMENT ON COLUMN student_groups.year_start_study IS 'Год начала обучения';
|
||||
COMMENT ON COLUMN student_groups.created_at IS 'Дата и время создания';
|
||||
|
||||
@@ -526,7 +645,7 @@ COMMENT ON COLUMN classrooms.created_at IS 'Дата и время создан
|
||||
|
||||
COMMENT ON COLUMN classroom_equipments.classroom_id IS 'ID аудитории';
|
||||
COMMENT ON COLUMN classroom_equipments.equipment_id IS 'ID оборудования';
|
||||
COMMENT ON COLUMN classroom_equipments.quantity IS 'Дата и время создания'; -- Так было в V2
|
||||
COMMENT ON COLUMN classroom_equipments.quantity IS 'Количество единиц оборудования';
|
||||
COMMENT ON COLUMN classroom_equipments.notes IS 'Примечания к записи';
|
||||
|
||||
COMMENT ON COLUMN teacher_subjects.user_id IS 'ID преподавателя';
|
||||
@@ -542,6 +661,37 @@ COMMENT ON COLUMN specialties.id IS 'ID специальности';
|
||||
COMMENT ON COLUMN specialties.name IS 'Название специальности';
|
||||
COMMENT ON COLUMN specialties.specialty_code IS 'Код специальности';
|
||||
|
||||
COMMENT ON COLUMN specialty_profiles.id IS 'ID профиля обучения';
|
||||
COMMENT ON COLUMN specialty_profiles.specialty_id IS 'ID специальности';
|
||||
COMMENT ON COLUMN specialty_profiles.name IS 'Название профиля обучения';
|
||||
COMMENT ON COLUMN specialty_profiles.description IS 'Описание профиля обучения';
|
||||
|
||||
COMMENT ON COLUMN calendar_study_forms.id IS 'ID формы обучения графика';
|
||||
COMMENT ON COLUMN calendar_study_forms.name IS 'Название формы обучения графика';
|
||||
COMMENT ON COLUMN calendar_study_forms.description IS 'Описание формы обучения графика';
|
||||
|
||||
COMMENT ON COLUMN academic_calendar_activity_types.code IS 'Код активности из календарного графика';
|
||||
COMMENT ON COLUMN academic_calendar_activity_types.name IS 'Название активности';
|
||||
COMMENT ON COLUMN academic_calendar_activity_types.allow_schedule IS 'Разрешает ли код генерацию обычного расписания';
|
||||
|
||||
COMMENT ON COLUMN academic_calendars.title IS 'Название календарного учебного графика';
|
||||
COMMENT ON COLUMN academic_calendars.academic_year_id IS 'ID учебного года';
|
||||
COMMENT ON COLUMN academic_calendars.specialty_id IS 'ID специальности';
|
||||
COMMENT ON COLUMN academic_calendars.specialty_profile_id IS 'ID профиля обучения';
|
||||
COMMENT ON COLUMN academic_calendars.study_form_id IS 'ID формы обучения графика';
|
||||
COMMENT ON COLUMN academic_calendars.course_count IS 'Количество курсов в графике';
|
||||
|
||||
COMMENT ON COLUMN academic_calendar_days.calendar_id IS 'ID календарного учебного графика';
|
||||
COMMENT ON COLUMN academic_calendar_days.course_number IS 'Номер курса';
|
||||
COMMENT ON COLUMN academic_calendar_days.date IS 'Дата';
|
||||
COMMENT ON COLUMN academic_calendar_days.week_number IS 'Номер недели в учебном году';
|
||||
COMMENT ON COLUMN academic_calendar_days.day_of_week IS 'День недели ISO';
|
||||
COMMENT ON COLUMN academic_calendar_days.activity_type_id IS 'ID кода активности';
|
||||
|
||||
COMMENT ON COLUMN student_group_calendar_assignments.group_id IS 'ID учебной группы';
|
||||
COMMENT ON COLUMN student_group_calendar_assignments.academic_year_id IS 'ID учебного года';
|
||||
COMMENT ON COLUMN student_group_calendar_assignments.calendar_id IS 'ID назначенного календарного графика';
|
||||
|
||||
COMMENT ON COLUMN teacher_lesson_types.user_id IS 'ID преподавателя';
|
||||
COMMENT ON COLUMN teacher_lesson_types.subject_id IS 'ID предмета';
|
||||
COMMENT ON COLUMN teacher_lesson_types.lesson_type_id IS 'ID типа занятия';
|
||||
|
||||
Reference in New Issue
Block a user