feat(backend): implement equipments entities and modify db

This commit is contained in:
Zuev
2026-02-20 02:53:47 +03:00
parent be46fa2be2
commit c552d14909
10 changed files with 431 additions and 8 deletions

View File

@@ -55,7 +55,7 @@ trigger: always_on
### 2. Управление ресурсами и топология ### 2. Управление ресурсами и топология
- **Управление аудиториями**: - **Управление аудиториями**:
- Указание вместимости. - Указание вместимости.
- Указание тэгов оборудования (Проектор, ПК, Лаборатория). - Привязка доступного оборудования (через сущность Equipments: Проектор, ПК, Лаборатория).
- Установка статуса "Не доступно" (блокирует назначение пар в этот период). - Установка статуса "Не доступно" (блокирует назначение пар в этот период).
- **Управление группами**: - **Управление группами**:
- Управление списком студентов (и возможность деления на подгруппы). - Управление списком студентов (и возможность деления на подгруппы).
@@ -97,7 +97,8 @@ trigger: always_on
## Основные сущности базы данных (Data Entities) ## Основные сущности базы данных (Data Entities)
- **Users**: Хранение пользователей и их ролей (Администратор, Преподаватель, Студент) для управления доступом. - **Users**: Хранение пользователей и их ролей (Администратор, Преподаватель, Студент) для управления доступом.
- **Groups**: Группы студентов, их привязка к формам обучения. (Могут делиться на **подгруппы** для лабораторных и практик). - **Groups**: Группы студентов, их привязка к формам обучения. (Могут делиться на **подгруппы** для лабораторных и практик).
- **Classrooms**: Аудиторный фонд (название, вместимость, статус доступности, тэги оборудования). - **Equipments**: Справочник оборудования.
- **Classrooms**: Аудиторный фонд (название, вместимость, статус доступности, привязанный список оборудования Equipments).
- **Subjects**: Предметы/Дисциплины (Высшая математика, Физика, Базы данных и т.д.). - **Subjects**: Предметы/Дисциплины (Высшая математика, Физика, Базы данных и т.д.).
- **Teacher_Subjects**: Связующая таблица (Many-to-Many), определяющая, какие дисциплины ведет конкретный преподаватель. - **Teacher_Subjects**: Связующая таблица (Many-to-Many), определяющая, какие дисциплины ведет конкретный преподаватель.
- **Lesson_Types**: Типы занятий для валидации (Лекция, Практика, Лабораторная работа). - **Lesson_Types**: Типы занятий для валидации (Лекция, Практика, Лабораторная работа).

View File

@@ -0,0 +1,109 @@
package com.magistr.app.controller;
import com.magistr.app.dto.ClassroomRequest;
import com.magistr.app.dto.ClassroomResponse;
import com.magistr.app.model.Classroom;
import com.magistr.app.model.Equipment;
import com.magistr.app.repository.ClassroomRepository;
import com.magistr.app.repository.EquipmentRepository;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@RestController
@RequestMapping("/api/classrooms")
public class ClassroomController {
private final ClassroomRepository classroomRepository;
private final EquipmentRepository equipmentRepository;
public ClassroomController(ClassroomRepository classroomRepository, EquipmentRepository equipmentRepository) {
this.classroomRepository = classroomRepository;
this.equipmentRepository = equipmentRepository;
}
@GetMapping
public List<ClassroomResponse> getAllClassrooms() {
return classroomRepository.findAll().stream()
.map(this::mapToResponse)
.toList();
}
@PostMapping
public ResponseEntity<?> createClassroom(@RequestBody ClassroomRequest request) {
if (request.getName() == null || request.getName().isBlank()) {
return ResponseEntity.badRequest().body(Map.of("message", "Название аудитории обязательно"));
}
if (request.getCapacity() == null || request.getCapacity() <= 0) {
return ResponseEntity.badRequest().body(Map.of("message", "Вместимость должна быть больше нуля"));
}
if (classroomRepository.findByName(request.getName().trim()).isPresent()) {
return ResponseEntity.badRequest().body(Map.of("message", "Аудитория с таким названием уже существует"));
}
Classroom classroom = new Classroom();
classroom.setName(request.getName().trim());
classroom.setCapacity(request.getCapacity());
classroom.setIsAvailable(request.getIsAvailable() != null ? request.getIsAvailable() : true);
if (request.getEquipmentIds() != null && !request.getEquipmentIds().isEmpty()) {
List<Equipment> equipments = equipmentRepository.findAllById(request.getEquipmentIds());
classroom.setEquipments(new java.util.HashSet<>(equipments));
}
classroomRepository.save(classroom);
return ResponseEntity.ok(mapToResponse(classroom));
}
@PutMapping("/{id}")
public ResponseEntity<?> updateClassroom(@PathVariable Long id, @RequestBody ClassroomRequest request) {
Optional<Classroom> opt = classroomRepository.findById(id);
if (opt.isEmpty()) {
return ResponseEntity.notFound().build();
}
Classroom classroom = opt.get();
if (request.getName() != null && !request.getName().isBlank()
&& !classroom.getName().equals(request.getName().trim())) {
if (classroomRepository.findByName(request.getName().trim()).isPresent()) {
return ResponseEntity.badRequest()
.body(Map.of("message", "Аудитория с таким названием уже существует"));
}
classroom.setName(request.getName().trim());
}
if (request.getCapacity() != null && request.getCapacity() > 0) {
classroom.setCapacity(request.getCapacity());
}
if (request.getIsAvailable() != null) {
classroom.setIsAvailable(request.getIsAvailable());
}
if (request.getEquipmentIds() != null) {
List<Equipment> equipments = equipmentRepository.findAllById(request.getEquipmentIds());
classroom.setEquipments(new java.util.HashSet<>(equipments));
}
classroomRepository.save(classroom);
return ResponseEntity.ok(mapToResponse(classroom));
}
@DeleteMapping("/{id}")
public ResponseEntity<?> deleteClassroom(@PathVariable Long id) {
if (!classroomRepository.existsById(id)) {
return ResponseEntity.notFound().build();
}
classroomRepository.deleteById(id);
return ResponseEntity.ok(Map.of("message", "Аудитория удалена"));
}
private ClassroomResponse mapToResponse(Classroom c) {
return new ClassroomResponse(c.getId(), c.getName(), c.getCapacity(), c.getIsAvailable(),
new java.util.ArrayList<>(c.getEquipments()));
}
}

View File

@@ -0,0 +1,51 @@
package com.magistr.app.controller;
import com.magistr.app.model.Equipment;
import com.magistr.app.repository.EquipmentRepository;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/equipments")
public class EquipmentController {
private final EquipmentRepository equipmentRepository;
public EquipmentController(EquipmentRepository equipmentRepository) {
this.equipmentRepository = equipmentRepository;
}
@GetMapping
public List<Equipment> getAllEquipments() {
return equipmentRepository.findAll();
}
@PostMapping
public ResponseEntity<?> createEquipment(@RequestBody Map<String, String> request) {
String name = request.get("name");
if (name == null || name.isBlank()) {
return ResponseEntity.badRequest().body(Map.of("message", "Название обязательно"));
}
if (equipmentRepository.findByName(name.trim()).isPresent()) {
return ResponseEntity.badRequest().body(Map.of("message", "Оборудование с таким названием уже существует"));
}
Equipment equipment = new Equipment();
equipment.setName(name.trim());
equipmentRepository.save(equipment);
return ResponseEntity.ok(equipment);
}
@DeleteMapping("/{id}")
public ResponseEntity<?> deleteEquipment(@PathVariable Long id) {
if (!equipmentRepository.existsById(id)) {
return ResponseEntity.notFound().build();
}
equipmentRepository.deleteById(id);
return ResponseEntity.ok(Map.of("message", "Оборудование удалено"));
}
}

View File

@@ -0,0 +1,42 @@
package com.magistr.app.dto;
import java.util.List;
public class ClassroomRequest {
private String name;
private Integer capacity;
private Boolean isAvailable;
private List<Long> equipmentIds;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getCapacity() {
return capacity;
}
public void setCapacity(Integer capacity) {
this.capacity = capacity;
}
public Boolean getIsAvailable() {
return isAvailable;
}
public void setIsAvailable(Boolean isAvailable) {
this.isAvailable = isAvailable;
}
public List<Long> getEquipmentIds() {
return equipmentIds;
}
public void setEquipmentIds(List<Long> equipmentIds) {
this.equipmentIds = equipmentIds;
}
}

View File

@@ -0,0 +1,63 @@
package com.magistr.app.dto;
import com.magistr.app.model.Equipment;
import java.util.List;
public class ClassroomResponse {
private Long id;
private String name;
private Integer capacity;
private Boolean isAvailable;
private List<Equipment> equipments;
public ClassroomResponse() {
}
public ClassroomResponse(Long id, String name, Integer capacity, Boolean isAvailable, List<Equipment> equipments) {
this.id = id;
this.name = name;
this.capacity = capacity;
this.isAvailable = isAvailable;
this.equipments = equipments;
}
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 Integer getCapacity() {
return capacity;
}
public void setCapacity(Integer capacity) {
this.capacity = capacity;
}
public Boolean getIsAvailable() {
return isAvailable;
}
public void setIsAvailable(Boolean isAvailable) {
this.isAvailable = isAvailable;
}
public List<Equipment> getEquipments() {
return equipments;
}
public void setEquipments(List<Equipment> equipments) {
this.equipments = equipments;
}
}

View File

@@ -0,0 +1,70 @@
package com.magistr.app.model;
import jakarta.persistence.*;
import java.util.HashSet;
import java.util.Set;
@Entity
@Table(name = "classrooms")
public class Classroom {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false, length = 50)
private String name;
@Column(nullable = false)
private Integer capacity;
@Column(name = "is_available", nullable = false)
private Boolean isAvailable = true;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "classroom_equipments", joinColumns = @JoinColumn(name = "classroom_id"), inverseJoinColumns = @JoinColumn(name = "equipment_id"))
private Set<Equipment> equipments = new HashSet<>();
public Classroom() {
}
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 Integer getCapacity() {
return capacity;
}
public void setCapacity(Integer capacity) {
this.capacity = capacity;
}
public Boolean getIsAvailable() {
return isAvailable;
}
public void setIsAvailable(Boolean isAvailable) {
this.isAvailable = isAvailable;
}
public Set<Equipment> getEquipments() {
return equipments;
}
public void setEquipments(Set<Equipment> equipments) {
this.equipments = equipments;
}
}

View File

@@ -0,0 +1,39 @@
package com.magistr.app.model;
import jakarta.persistence.*;
@Entity
@Table(name = "equipments")
public class Equipment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false, length = 50)
private String name;
public Equipment() {
}
public Equipment(Long id, String name) {
this.id = id;
this.name = name;
}
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;
}
}

View File

@@ -0,0 +1,10 @@
package com.magistr.app.repository;
import com.magistr.app.model.Classroom;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface ClassroomRepository extends JpaRepository<Classroom, Long> {
Optional<Classroom> findByName(String name);
}

View File

@@ -0,0 +1,10 @@
package com.magistr.app.repository;
import com.magistr.app.model.Equipment;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface EquipmentRepository extends JpaRepository<Equipment, Long> {
Optional<Equipment> findByName(String name);
}

View File

@@ -55,21 +55,49 @@ INSERT INTO lesson_types (name) VALUES
('Лабораторная работа') ('Лабораторная работа')
ON CONFLICT (name) DO NOTHING; ON CONFLICT (name) DO NOTHING;
-- Оборудование
CREATE TABLE IF NOT EXISTS equipments (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(50) UNIQUE NOT NULL
);
INSERT INTO equipments (name) VALUES
('Проектор'),
('ПК'),
('Лаборатория'),
('Интерактивная доска')
ON CONFLICT (name) DO NOTHING;
-- Аудитории -- Аудитории
CREATE TABLE IF NOT EXISTS classrooms ( CREATE TABLE IF NOT EXISTS classrooms (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
name VARCHAR(50) UNIQUE NOT NULL, name VARCHAR(50) UNIQUE NOT NULL,
capacity INT NOT NULL, capacity INT NOT NULL,
is_available BOOLEAN DEFAULT TRUE, is_available BOOLEAN DEFAULT TRUE
hardware_tags VARCHAR(255) -- например: "Проектор, ПК, Лаборатория"
); );
INSERT INTO classrooms (name, capacity, hardware_tags) VALUES INSERT INTO classrooms (name, capacity) VALUES
('101 Ленинская', 120, 'Проектор, Доска'), ('101 Ленинская', 120),
('202 IT Lab', 20, 'ПК, Проектор, Лаборатория'), ('202 IT Lab', 20),
('303 Обычная', 30, 'Доска') ('303 Обычная', 30)
ON CONFLICT (name) DO NOTHING; ON CONFLICT (name) DO NOTHING;
-- Привязка оборудования к аудиториям (Many-to-Many)
CREATE TABLE IF NOT EXISTS classroom_equipments (
classroom_id BIGINT NOT NULL REFERENCES classrooms(id) ON DELETE CASCADE,
equipment_id BIGINT NOT NULL REFERENCES equipments(id) ON DELETE CASCADE,
PRIMARY KEY (classroom_id, equipment_id)
);
-- Заполнение привязок оборудования (на основе ID базовых данных)
-- '101 Ленинская' -> Проектор (1), Интерактивная доска (4)
INSERT INTO classroom_equipments (classroom_id, equipment_id) VALUES
(1, 1), (1, 4),
-- '202 IT Lab' -> ПК (2), Проектор (1), Лаборатория (3)
(2, 2), (2, 1), (2, 3),
-- '303 Обычная' -> ничего
ON CONFLICT DO NOTHING;
-- ========================================== -- ==========================================
-- Связи для преподавателей и студентов -- Связи для преподавателей и студентов
-- ========================================== -- ==========================================