сделал настройку временных слотов
This commit is contained in:
@@ -47,6 +47,8 @@ public class ClassroomController {
|
||||
Classroom classroom = new Classroom();
|
||||
classroom.setName(request.getName().trim());
|
||||
classroom.setCapacity(request.getCapacity());
|
||||
classroom.setBuilding(cleanText(request.getBuilding()));
|
||||
classroom.setFloor(request.getFloor());
|
||||
classroom.setIsAvailable(request.getIsAvailable() != null ? request.getIsAvailable() : true);
|
||||
|
||||
if (request.getEquipmentIds() != null && !request.getEquipmentIds().isEmpty()) {
|
||||
@@ -80,6 +82,14 @@ public class ClassroomController {
|
||||
classroom.setCapacity(request.getCapacity());
|
||||
}
|
||||
|
||||
if (request.getBuilding() != null) {
|
||||
classroom.setBuilding(cleanText(request.getBuilding()));
|
||||
}
|
||||
|
||||
if (request.getFloor() != null) {
|
||||
classroom.setFloor(request.getFloor());
|
||||
}
|
||||
|
||||
if (request.getIsAvailable() != null) {
|
||||
classroom.setIsAvailable(request.getIsAvailable());
|
||||
}
|
||||
@@ -103,7 +113,11 @@ public class ClassroomController {
|
||||
}
|
||||
|
||||
private ClassroomResponse mapToResponse(Classroom c) {
|
||||
return new ClassroomResponse(c.getId(), c.getName(), c.getCapacity(), c.getIsAvailable(),
|
||||
return new ClassroomResponse(c.getId(), c.getName(), c.getCapacity(), c.getBuilding(), c.getFloor(), c.getIsAvailable(),
|
||||
new java.util.ArrayList<>(c.getEquipments()));
|
||||
}
|
||||
|
||||
private String cleanText(String value) {
|
||||
return value == null || value.isBlank() ? null : value.trim();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ import java.util.*;
|
||||
@RequestMapping("/api/admin/schedule-rules")
|
||||
public class ScheduleRuleAdminController {
|
||||
|
||||
private static final String APPLY_MODE_DEFAULT = "DEFAULT";
|
||||
|
||||
private final ScheduleRuleRepository scheduleRuleRepository;
|
||||
private final SubjectRepository subjectRepository;
|
||||
private final SemesterRepository semesterRepository;
|
||||
@@ -169,6 +171,10 @@ public class ScheduleRuleAdminController {
|
||||
}
|
||||
TimeSlot timeSlot = timeSlotRepository.findById(slotDto.timeSlotId())
|
||||
.orElseThrow(() -> new IllegalArgumentException("Временной слот не найден"));
|
||||
if (timeSlot.getTimeSlotScope() == null
|
||||
|| !APPLY_MODE_DEFAULT.equals(timeSlot.getTimeSlotScope().getApplyMode())) {
|
||||
throw new IllegalArgumentException("В правилах расписания можно выбирать только базовые временные слоты");
|
||||
}
|
||||
User teacher = userRepository.findById(slotDto.teacherId())
|
||||
.orElseThrow(() -> new IllegalArgumentException("Преподаватель не найден"));
|
||||
Classroom classroom = classroomRepository.findById(slotDto.classroomId())
|
||||
|
||||
@@ -1,40 +1,159 @@
|
||||
package com.magistr.app.controller;
|
||||
|
||||
import com.magistr.app.dto.TimeSlotDateAssignmentDto;
|
||||
import com.magistr.app.dto.TimeSlotDto;
|
||||
import com.magistr.app.dto.TimeSlotScopeDto;
|
||||
import com.magistr.app.model.TimeSlot;
|
||||
import com.magistr.app.model.TimeSlotDateAssignment;
|
||||
import com.magistr.app.model.TimeSlotScope;
|
||||
import com.magistr.app.repository.TimeSlotDateAssignmentRepository;
|
||||
import com.magistr.app.repository.TimeSlotRepository;
|
||||
import com.magistr.app.repository.TimeSlotScopeRepository;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/admin/time-slots")
|
||||
public class TimeSlotAdminController {
|
||||
|
||||
private final TimeSlotRepository timeSlotRepository;
|
||||
private static final String APPLY_MODE_DEFAULT = "DEFAULT";
|
||||
private static final String APPLY_MODE_WEEKDAY = "WEEKDAY";
|
||||
private static final String APPLY_MODE_MANUAL = "MANUAL";
|
||||
|
||||
public TimeSlotAdminController(TimeSlotRepository timeSlotRepository) {
|
||||
private final TimeSlotRepository timeSlotRepository;
|
||||
private final TimeSlotScopeRepository timeSlotScopeRepository;
|
||||
private final TimeSlotDateAssignmentRepository dateAssignmentRepository;
|
||||
|
||||
public TimeSlotAdminController(TimeSlotRepository timeSlotRepository,
|
||||
TimeSlotScopeRepository timeSlotScopeRepository,
|
||||
TimeSlotDateAssignmentRepository dateAssignmentRepository) {
|
||||
this.timeSlotRepository = timeSlotRepository;
|
||||
this.timeSlotScopeRepository = timeSlotScopeRepository;
|
||||
this.dateAssignmentRepository = dateAssignmentRepository;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public List<TimeSlotDto> getAll() {
|
||||
return timeSlotRepository.findAllByOrderByOrderNumberAsc().stream()
|
||||
return timeSlotRepository.findAllByOrderByTimeSlotScopeDisplayOrderAscOrderNumberAscStartTimeAsc().stream()
|
||||
.map(this::toDto)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@GetMapping("/effective")
|
||||
public ResponseEntity<?> getEffective(@RequestParam LocalDate date) {
|
||||
return ResponseEntity.ok(effectiveSlots(date).stream().map(this::toDto).toList());
|
||||
}
|
||||
|
||||
@GetMapping("/scopes")
|
||||
public List<TimeSlotScopeDto> getScopes() {
|
||||
return timeSlotScopeRepository.findAllByOrderByDisplayOrderAscNameAsc().stream()
|
||||
.map(this::toScopeDto)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@PostMapping("/scopes")
|
||||
public ResponseEntity<?> createScope(@RequestBody TimeSlotScopeDto request) {
|
||||
if (request.name() == null || request.name().isBlank()) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", "Название сетки обязательно"));
|
||||
}
|
||||
|
||||
TimeSlotScope scope = new TimeSlotScope();
|
||||
scope.setCode("manual_" + UUID.randomUUID().toString().replace("-", ""));
|
||||
scope.setName(request.name().trim());
|
||||
scope.setApplyMode(APPLY_MODE_MANUAL);
|
||||
scope.setDayOfWeek(null);
|
||||
scope.setSystemScope(false);
|
||||
scope.setDisplayOrder(nextManualDisplayOrder());
|
||||
return ResponseEntity.ok(toScopeDto(timeSlotScopeRepository.save(scope)));
|
||||
}
|
||||
|
||||
@PutMapping("/scopes/{id}")
|
||||
public ResponseEntity<?> updateScope(@PathVariable Long id, @RequestBody TimeSlotScopeDto request) {
|
||||
TimeSlotScope scope = timeSlotScopeRepository.findById(id).orElse(null);
|
||||
if (scope == null) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
if (Boolean.TRUE.equals(scope.getSystemScope())) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", "Системную сетку нельзя изменять"));
|
||||
}
|
||||
if (request.name() == null || request.name().isBlank()) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", "Название сетки обязательно"));
|
||||
}
|
||||
scope.setName(request.name().trim());
|
||||
return ResponseEntity.ok(toScopeDto(timeSlotScopeRepository.save(scope)));
|
||||
}
|
||||
|
||||
@DeleteMapping("/scopes/{id}")
|
||||
public ResponseEntity<?> deleteScope(@PathVariable Long id) {
|
||||
TimeSlotScope scope = timeSlotScopeRepository.findById(id).orElse(null);
|
||||
if (scope == null) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
if (Boolean.TRUE.equals(scope.getSystemScope())) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", "Системную сетку нельзя удалить"));
|
||||
}
|
||||
timeSlotScopeRepository.delete(scope);
|
||||
return ResponseEntity.ok(Map.of("message", "Сетка времени удалена"));
|
||||
}
|
||||
|
||||
@GetMapping("/date-assignments")
|
||||
public List<TimeSlotDateAssignmentDto> getDateAssignments(@RequestParam(required = false) LocalDate startDate,
|
||||
@RequestParam(required = false) LocalDate endDate) {
|
||||
List<TimeSlotDateAssignment> assignments = startDate != null && endDate != null
|
||||
? dateAssignmentRepository.findByDateBetweenOrderByDateAsc(startDate, endDate)
|
||||
: dateAssignmentRepository.findAllByOrderByDateAsc();
|
||||
return assignments.stream().map(this::toAssignmentDto).toList();
|
||||
}
|
||||
|
||||
@PostMapping("/date-assignments")
|
||||
public ResponseEntity<?> saveDateAssignment(@RequestBody TimeSlotDateAssignmentDto request) {
|
||||
if (request.date() == null) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", "Дата обязательна"));
|
||||
}
|
||||
if (request.scopeId() == null) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", "Сетка времени обязательна"));
|
||||
}
|
||||
TimeSlotScope scope = timeSlotScopeRepository.findById(request.scopeId()).orElse(null);
|
||||
if (scope == null) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", "Сетка времени не найдена"));
|
||||
}
|
||||
if (!APPLY_MODE_MANUAL.equals(scope.getApplyMode())) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", "Вручную можно применять только пользовательские сетки"));
|
||||
}
|
||||
|
||||
TimeSlotDateAssignment assignment = dateAssignmentRepository.findByDate(request.date())
|
||||
.orElseGet(TimeSlotDateAssignment::new);
|
||||
assignment.setDate(request.date());
|
||||
assignment.setTimeSlotScope(scope);
|
||||
return ResponseEntity.ok(toAssignmentDto(dateAssignmentRepository.save(assignment)));
|
||||
}
|
||||
|
||||
@DeleteMapping("/date-assignments/{id}")
|
||||
public ResponseEntity<?> deleteDateAssignment(@PathVariable Long id) {
|
||||
if (!dateAssignmentRepository.existsById(id)) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
dateAssignmentRepository.deleteById(id);
|
||||
return ResponseEntity.ok(Map.of("message", "Ручное назначение удалено"));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<?> create(@RequestBody TimeSlotDto request) {
|
||||
String validationError = validate(request);
|
||||
if (validationError != null) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", validationError));
|
||||
}
|
||||
if (timeSlotRepository.existsByOrderNumber(request.orderNumber())) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", "Слот с таким номером пары уже существует"));
|
||||
if (hasDuplicate(request, null)) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", "В выбранной сетке уже есть слот с таким номером пары"));
|
||||
}
|
||||
|
||||
TimeSlot timeSlot = new TimeSlot();
|
||||
@@ -53,9 +172,8 @@ public class TimeSlotAdminController {
|
||||
if (validationError != null) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", validationError));
|
||||
}
|
||||
if (timeSlotRepository.existsByOrderNumber(request.orderNumber())
|
||||
&& !timeSlot.getOrderNumber().equals(request.orderNumber())) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", "Слот с таким номером пары уже существует"));
|
||||
if (hasDuplicate(request, id)) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", "В выбранной сетке уже есть слот с таким номером пары"));
|
||||
}
|
||||
|
||||
apply(timeSlot, request);
|
||||
@@ -67,14 +185,21 @@ public class TimeSlotAdminController {
|
||||
if (!timeSlotRepository.existsById(id)) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
timeSlotRepository.deleteById(id);
|
||||
return ResponseEntity.ok(Map.of("message", "Временной слот удалён"));
|
||||
try {
|
||||
timeSlotRepository.deleteById(id);
|
||||
return ResponseEntity.ok(Map.of("message", "Временной слот удалён"));
|
||||
} catch (DataIntegrityViolationException e) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", "Нельзя удалить слот, который используется в правилах расписания"));
|
||||
}
|
||||
}
|
||||
|
||||
private String validate(TimeSlotDto request) {
|
||||
if (request.orderNumber() == null || request.orderNumber() <= 0) {
|
||||
return "Номер пары должен быть больше нуля";
|
||||
}
|
||||
if (request.scopeId() == null || timeSlotScopeRepository.findById(request.scopeId()).isEmpty()) {
|
||||
return "Выберите сетку времени";
|
||||
}
|
||||
if (request.startTime() == null || request.endTime() == null) {
|
||||
return "Время начала и окончания обязательно";
|
||||
}
|
||||
@@ -86,6 +211,8 @@ public class TimeSlotAdminController {
|
||||
|
||||
private void apply(TimeSlot timeSlot, TimeSlotDto request) {
|
||||
timeSlot.setOrderNumber(request.orderNumber());
|
||||
timeSlot.setTimeSlotScope(timeSlotScopeRepository.findById(request.scopeId())
|
||||
.orElseThrow(() -> new IllegalArgumentException("Сетка времени не найдена")));
|
||||
timeSlot.setStartTime(request.startTime());
|
||||
timeSlot.setEndTime(request.endTime());
|
||||
int duration = request.durationMinutes() != null && request.durationMinutes() > 0
|
||||
@@ -94,13 +221,83 @@ public class TimeSlotAdminController {
|
||||
timeSlot.setDurationMinutes(duration);
|
||||
}
|
||||
|
||||
private boolean hasDuplicate(TimeSlotDto request, Long currentId) {
|
||||
return timeSlotRepository.findByTimeSlotScopeIdAndOrderNumber(request.scopeId(), request.orderNumber())
|
||||
.filter(existing -> currentId == null || !existing.getId().equals(currentId))
|
||||
.isPresent();
|
||||
}
|
||||
|
||||
private List<TimeSlot> effectiveSlots(LocalDate date) {
|
||||
TimeSlotScope defaultScope = defaultScope();
|
||||
TimeSlotScope effectiveScope = effectiveScope(date);
|
||||
List<TimeSlot> defaultSlots = timeSlotRepository.findAllByTimeSlotScopeIdOrderByOrderNumberAsc(defaultScope.getId());
|
||||
if (effectiveScope.getId().equals(defaultScope.getId())) {
|
||||
return defaultSlots;
|
||||
}
|
||||
|
||||
Map<Integer, TimeSlot> slotsByOrder = new LinkedHashMap<>();
|
||||
defaultSlots.forEach(slot -> slotsByOrder.put(slot.getOrderNumber(), slot));
|
||||
timeSlotRepository.findAllByTimeSlotScopeIdOrderByOrderNumberAsc(effectiveScope.getId())
|
||||
.forEach(slot -> slotsByOrder.put(slot.getOrderNumber(), slot));
|
||||
return new ArrayList<>(slotsByOrder.values());
|
||||
}
|
||||
|
||||
private TimeSlotScope effectiveScope(LocalDate date) {
|
||||
return dateAssignmentRepository.findByDate(date)
|
||||
.map(TimeSlotDateAssignment::getTimeSlotScope)
|
||||
.or(() -> timeSlotScopeRepository.findByApplyModeAndDayOfWeek(
|
||||
APPLY_MODE_WEEKDAY,
|
||||
date.getDayOfWeek().getValue()))
|
||||
.orElseGet(this::defaultScope);
|
||||
}
|
||||
|
||||
private TimeSlotScope defaultScope() {
|
||||
return timeSlotScopeRepository.findFirstByApplyModeOrderByDisplayOrderAsc(APPLY_MODE_DEFAULT)
|
||||
.orElseThrow(() -> new IllegalStateException("Базовая сетка времени не настроена"));
|
||||
}
|
||||
|
||||
private int nextManualDisplayOrder() {
|
||||
return timeSlotScopeRepository.findAllByOrderByDisplayOrderAscNameAsc().stream()
|
||||
.map(TimeSlotScope::getDisplayOrder)
|
||||
.filter(value -> value != null)
|
||||
.max(Integer::compareTo)
|
||||
.orElse(100) + 10;
|
||||
}
|
||||
|
||||
private TimeSlotDto toDto(TimeSlot timeSlot) {
|
||||
TimeSlotScope scope = timeSlot.getTimeSlotScope();
|
||||
return new TimeSlotDto(
|
||||
timeSlot.getId(),
|
||||
timeSlot.getOrderNumber(),
|
||||
scope == null ? null : scope.getId(),
|
||||
scope == null ? null : scope.getName(),
|
||||
scope == null ? null : scope.getApplyMode(),
|
||||
scope == null ? null : scope.getDayOfWeek(),
|
||||
timeSlot.getStartTime(),
|
||||
timeSlot.getEndTime(),
|
||||
timeSlot.getDurationMinutes()
|
||||
);
|
||||
}
|
||||
|
||||
private TimeSlotScopeDto toScopeDto(TimeSlotScope scope) {
|
||||
return new TimeSlotScopeDto(
|
||||
scope.getId(),
|
||||
scope.getCode(),
|
||||
scope.getName(),
|
||||
scope.getApplyMode(),
|
||||
scope.getDayOfWeek(),
|
||||
scope.getSystemScope(),
|
||||
scope.getDisplayOrder()
|
||||
);
|
||||
}
|
||||
|
||||
private TimeSlotDateAssignmentDto toAssignmentDto(TimeSlotDateAssignment assignment) {
|
||||
TimeSlotScope scope = assignment.getTimeSlotScope();
|
||||
return new TimeSlotDateAssignmentDto(
|
||||
assignment.getId(),
|
||||
assignment.getDate(),
|
||||
scope.getId(),
|
||||
scope.getName()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import java.util.List;
|
||||
public class ClassroomRequest {
|
||||
private String name;
|
||||
private Integer capacity;
|
||||
private String building;
|
||||
private Integer floor;
|
||||
private Boolean isAvailable;
|
||||
private List<Long> equipmentIds;
|
||||
|
||||
@@ -24,6 +26,22 @@ public class ClassroomRequest {
|
||||
this.capacity = capacity;
|
||||
}
|
||||
|
||||
public String getBuilding() {
|
||||
return building;
|
||||
}
|
||||
|
||||
public void setBuilding(String building) {
|
||||
this.building = building;
|
||||
}
|
||||
|
||||
public Integer getFloor() {
|
||||
return floor;
|
||||
}
|
||||
|
||||
public void setFloor(Integer floor) {
|
||||
this.floor = floor;
|
||||
}
|
||||
|
||||
public Boolean getIsAvailable() {
|
||||
return isAvailable;
|
||||
}
|
||||
|
||||
@@ -7,16 +7,21 @@ public class ClassroomResponse {
|
||||
private Long id;
|
||||
private String name;
|
||||
private Integer capacity;
|
||||
private String building;
|
||||
private Integer floor;
|
||||
private Boolean isAvailable;
|
||||
private List<Equipment> equipments;
|
||||
|
||||
public ClassroomResponse() {
|
||||
}
|
||||
|
||||
public ClassroomResponse(Long id, String name, Integer capacity, Boolean isAvailable, List<Equipment> equipments) {
|
||||
public ClassroomResponse(Long id, String name, Integer capacity, String building, Integer floor,
|
||||
Boolean isAvailable, List<Equipment> equipments) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.capacity = capacity;
|
||||
this.building = building;
|
||||
this.floor = floor;
|
||||
this.isAvailable = isAvailable;
|
||||
this.equipments = equipments;
|
||||
}
|
||||
@@ -45,6 +50,22 @@ public class ClassroomResponse {
|
||||
this.capacity = capacity;
|
||||
}
|
||||
|
||||
public String getBuilding() {
|
||||
return building;
|
||||
}
|
||||
|
||||
public void setBuilding(String building) {
|
||||
this.building = building;
|
||||
}
|
||||
|
||||
public Integer getFloor() {
|
||||
return floor;
|
||||
}
|
||||
|
||||
public void setFloor(Integer floor) {
|
||||
this.floor = floor;
|
||||
}
|
||||
|
||||
public Boolean getIsAvailable() {
|
||||
return isAvailable;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.magistr.app.dto;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
public record TimeSlotDateAssignmentDto(
|
||||
Long id,
|
||||
LocalDate date,
|
||||
Long scopeId,
|
||||
String scopeName
|
||||
) {
|
||||
}
|
||||
@@ -5,6 +5,10 @@ import java.time.LocalTime;
|
||||
public record TimeSlotDto(
|
||||
Long id,
|
||||
Integer orderNumber,
|
||||
Long scopeId,
|
||||
String scopeName,
|
||||
String scopeApplyMode,
|
||||
Integer scopeDayOfWeek,
|
||||
LocalTime startTime,
|
||||
LocalTime endTime,
|
||||
Integer durationMinutes
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.magistr.app.dto;
|
||||
|
||||
public record TimeSlotScopeDto(
|
||||
Long id,
|
||||
String code,
|
||||
String name,
|
||||
String applyMode,
|
||||
Integer dayOfWeek,
|
||||
Boolean systemScope,
|
||||
Integer displayOrder
|
||||
) {
|
||||
}
|
||||
@@ -18,6 +18,11 @@ public class Classroom {
|
||||
@Column(nullable = false)
|
||||
private Integer capacity;
|
||||
|
||||
@Column(length = 50)
|
||||
private String building;
|
||||
|
||||
private Integer floor;
|
||||
|
||||
@Column(name = "is_available", nullable = false)
|
||||
private Boolean isAvailable = true;
|
||||
|
||||
@@ -52,6 +57,22 @@ public class Classroom {
|
||||
this.capacity = capacity;
|
||||
}
|
||||
|
||||
public String getBuilding() {
|
||||
return building;
|
||||
}
|
||||
|
||||
public void setBuilding(String building) {
|
||||
this.building = building;
|
||||
}
|
||||
|
||||
public Integer getFloor() {
|
||||
return floor;
|
||||
}
|
||||
|
||||
public void setFloor(Integer floor) {
|
||||
this.floor = floor;
|
||||
}
|
||||
|
||||
public Boolean getIsAvailable() {
|
||||
return isAvailable;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,10 @@ public class TimeSlot {
|
||||
@Column(name = "order_number", nullable = false)
|
||||
private Integer orderNumber;
|
||||
|
||||
@ManyToOne(optional = false)
|
||||
@JoinColumn(name = "time_slot_scope_id", nullable = false)
|
||||
private TimeSlotScope timeSlotScope;
|
||||
|
||||
@Column(name = "start_time", nullable = false)
|
||||
private LocalTime startTime;
|
||||
|
||||
@@ -40,6 +44,14 @@ public class TimeSlot {
|
||||
this.orderNumber = orderNumber;
|
||||
}
|
||||
|
||||
public TimeSlotScope getTimeSlotScope() {
|
||||
return timeSlotScope;
|
||||
}
|
||||
|
||||
public void setTimeSlotScope(TimeSlotScope timeSlotScope) {
|
||||
this.timeSlotScope = timeSlotScope;
|
||||
}
|
||||
|
||||
public LocalTime getStartTime() {
|
||||
return startTime;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.magistr.app.model;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Entity
|
||||
@Table(name = "time_slot_date_assignments")
|
||||
public class TimeSlotDateAssignment {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "assignment_date", nullable = false, unique = true)
|
||||
private LocalDate date;
|
||||
|
||||
@ManyToOne(optional = false)
|
||||
@JoinColumn(name = "time_slot_scope_id", nullable = false)
|
||||
private TimeSlotScope timeSlotScope;
|
||||
|
||||
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 TimeSlotScope getTimeSlotScope() {
|
||||
return timeSlotScope;
|
||||
}
|
||||
|
||||
public void setTimeSlotScope(TimeSlotScope timeSlotScope) {
|
||||
this.timeSlotScope = timeSlotScope;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.magistr.app.model;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
@Entity
|
||||
@Table(name = "time_slot_scopes")
|
||||
public class TimeSlotScope {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(nullable = false, unique = true, length = 50)
|
||||
private String code;
|
||||
|
||||
@Column(nullable = false, length = 120)
|
||||
private String name;
|
||||
|
||||
@Column(name = "apply_mode", nullable = false, length = 20)
|
||||
private String applyMode;
|
||||
|
||||
@Column(name = "day_of_week")
|
||||
private Integer dayOfWeek;
|
||||
|
||||
@Column(name = "system_scope", nullable = false)
|
||||
private Boolean systemScope = false;
|
||||
|
||||
@Column(name = "display_order", nullable = false)
|
||||
private Integer displayOrder = 100;
|
||||
|
||||
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 String getApplyMode() {
|
||||
return applyMode;
|
||||
}
|
||||
|
||||
public void setApplyMode(String applyMode) {
|
||||
this.applyMode = applyMode;
|
||||
}
|
||||
|
||||
public Integer getDayOfWeek() {
|
||||
return dayOfWeek;
|
||||
}
|
||||
|
||||
public void setDayOfWeek(Integer dayOfWeek) {
|
||||
this.dayOfWeek = dayOfWeek;
|
||||
}
|
||||
|
||||
public Boolean getSystemScope() {
|
||||
return systemScope;
|
||||
}
|
||||
|
||||
public void setSystemScope(Boolean systemScope) {
|
||||
this.systemScope = systemScope;
|
||||
}
|
||||
|
||||
public Integer getDisplayOrder() {
|
||||
return displayOrder;
|
||||
}
|
||||
|
||||
public void setDisplayOrder(Integer displayOrder) {
|
||||
this.displayOrder = displayOrder;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.magistr.app.repository;
|
||||
|
||||
import com.magistr.app.model.TimeSlotDateAssignment;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface TimeSlotDateAssignmentRepository extends JpaRepository<TimeSlotDateAssignment, Long> {
|
||||
|
||||
Optional<TimeSlotDateAssignment> findByDate(LocalDate date);
|
||||
|
||||
List<TimeSlotDateAssignment> findAllByOrderByDateAsc();
|
||||
|
||||
List<TimeSlotDateAssignment> findByDateBetweenOrderByDateAsc(LocalDate startDate, LocalDate endDate);
|
||||
|
||||
boolean existsByTimeSlotScopeId(Long scopeId);
|
||||
}
|
||||
@@ -4,10 +4,15 @@ import com.magistr.app.model.TimeSlot;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface TimeSlotRepository extends JpaRepository<TimeSlot, Long> {
|
||||
|
||||
List<TimeSlot> findAllByOrderByOrderNumberAsc();
|
||||
List<TimeSlot> findAllByOrderByTimeSlotScopeDisplayOrderAscOrderNumberAscStartTimeAsc();
|
||||
|
||||
boolean existsByOrderNumber(Integer orderNumber);
|
||||
List<TimeSlot> findAllByTimeSlotScopeIdOrderByOrderNumberAsc(Long scopeId);
|
||||
|
||||
Optional<TimeSlot> findByTimeSlotScopeIdAndOrderNumber(Long scopeId, Integer orderNumber);
|
||||
|
||||
boolean existsByTimeSlotScopeId(Long scopeId);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.magistr.app.repository;
|
||||
|
||||
import com.magistr.app.model.TimeSlotScope;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface TimeSlotScopeRepository extends JpaRepository<TimeSlotScope, Long> {
|
||||
|
||||
List<TimeSlotScope> findAllByOrderByDisplayOrderAscNameAsc();
|
||||
|
||||
Optional<TimeSlotScope> findByCode(String code);
|
||||
|
||||
Optional<TimeSlotScope> findFirstByApplyModeOrderByDisplayOrderAsc(String applyMode);
|
||||
|
||||
Optional<TimeSlotScope> findByApplyModeAndDayOfWeek(String applyMode, Integer dayOfWeek);
|
||||
}
|
||||
@@ -4,6 +4,9 @@ import com.magistr.app.dto.RenderedLessonDto;
|
||||
import com.magistr.app.model.*;
|
||||
import com.magistr.app.repository.GroupRepository;
|
||||
import com.magistr.app.repository.ScheduleRuleRepository;
|
||||
import com.magistr.app.repository.TimeSlotDateAssignmentRepository;
|
||||
import com.magistr.app.repository.TimeSlotRepository;
|
||||
import com.magistr.app.repository.TimeSlotScopeRepository;
|
||||
import com.magistr.app.repository.UserRepository;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -22,21 +25,31 @@ 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;
|
||||
private static final String APPLY_MODE_WEEKDAY = "WEEKDAY";
|
||||
|
||||
private final ScheduleRuleRepository scheduleRuleRepository;
|
||||
private final GroupRepository groupRepository;
|
||||
private final UserRepository userRepository;
|
||||
private final AcademicDateService academicDateService;
|
||||
private final TimeSlotRepository timeSlotRepository;
|
||||
private final TimeSlotScopeRepository timeSlotScopeRepository;
|
||||
private final TimeSlotDateAssignmentRepository dateAssignmentRepository;
|
||||
private final Map<String, Integer> consumedHoursCache = new ConcurrentHashMap<>();
|
||||
|
||||
public ScheduleGeneratorService(ScheduleRuleRepository scheduleRuleRepository,
|
||||
GroupRepository groupRepository,
|
||||
UserRepository userRepository,
|
||||
AcademicDateService academicDateService) {
|
||||
AcademicDateService academicDateService,
|
||||
TimeSlotRepository timeSlotRepository,
|
||||
TimeSlotScopeRepository timeSlotScopeRepository,
|
||||
TimeSlotDateAssignmentRepository dateAssignmentRepository) {
|
||||
this.scheduleRuleRepository = scheduleRuleRepository;
|
||||
this.groupRepository = groupRepository;
|
||||
this.userRepository = userRepository;
|
||||
this.academicDateService = academicDateService;
|
||||
this.timeSlotRepository = timeSlotRepository;
|
||||
this.timeSlotScopeRepository = timeSlotScopeRepository;
|
||||
this.dateAssignmentRepository = dateAssignmentRepository;
|
||||
}
|
||||
|
||||
public List<RenderedLessonDto> buildScheduleForGroup(Long groupId, LocalDate startDate, LocalDate endDate) {
|
||||
@@ -247,7 +260,7 @@ public class ScheduleGeneratorService {
|
||||
List<StudentGroup> groups,
|
||||
int consumedBeforeLesson,
|
||||
int remainingAfterLesson) {
|
||||
TimeSlot timeSlot = slot.getTimeSlot();
|
||||
TimeSlot timeSlot = resolveEffectiveTimeSlot(slot.getTimeSlot(), date);
|
||||
List<StudentGroup> sortedGroups = groups.stream()
|
||||
.sorted(Comparator.comparing(StudentGroup::getName))
|
||||
.toList();
|
||||
@@ -283,6 +296,25 @@ public class ScheduleGeneratorService {
|
||||
);
|
||||
}
|
||||
|
||||
private TimeSlot resolveEffectiveTimeSlot(TimeSlot baseSlot, LocalDate date) {
|
||||
if (baseSlot == null || baseSlot.getOrderNumber() == null) {
|
||||
return baseSlot;
|
||||
}
|
||||
return effectiveScope(date)
|
||||
.flatMap(scope -> timeSlotRepository.findByTimeSlotScopeIdAndOrderNumber(
|
||||
scope.getId(),
|
||||
baseSlot.getOrderNumber()))
|
||||
.orElse(baseSlot);
|
||||
}
|
||||
|
||||
private Optional<TimeSlotScope> effectiveScope(LocalDate date) {
|
||||
return dateAssignmentRepository.findByDate(date)
|
||||
.map(TimeSlotDateAssignment::getTimeSlotScope)
|
||||
.or(() -> timeSlotScopeRepository.findByApplyModeAndDayOfWeek(
|
||||
APPLY_MODE_WEEKDAY,
|
||||
date.getDayOfWeek().getValue()));
|
||||
}
|
||||
|
||||
public static String dayName(DayOfWeek dayOfWeek) {
|
||||
String value = dayOfWeek.getDisplayName(TextStyle.FULL, Locale.forLanguageTag("ru-RU"));
|
||||
return value.substring(0, 1).toUpperCase(Locale.forLanguageTag("ru-RU")) + value.substring(1);
|
||||
|
||||
Reference in New Issue
Block a user