From c552d14909a2e507e28a33117c7ec2ba2567dd5f Mon Sep 17 00:00:00 2001 From: Zuev Date: Fri, 20 Feb 2026 02:53:47 +0300 Subject: [PATCH] feat(backend): implement equipments entities and modify db --- .agent/rules/main.md | 5 +- .../app/controller/ClassroomController.java | 109 ++++++++++++++++++ .../app/controller/EquipmentController.java | 51 ++++++++ .../com/magistr/app/dto/ClassroomRequest.java | 42 +++++++ .../magistr/app/dto/ClassroomResponse.java | 63 ++++++++++ .../java/com/magistr/app/model/Classroom.java | 70 +++++++++++ .../java/com/magistr/app/model/Equipment.java | 39 +++++++ .../app/repository/ClassroomRepository.java | 10 ++ .../app/repository/EquipmentRepository.java | 10 ++ db/init/init.sql | 40 ++++++- 10 files changed, 431 insertions(+), 8 deletions(-) create mode 100644 backend/src/main/java/com/magistr/app/controller/ClassroomController.java create mode 100644 backend/src/main/java/com/magistr/app/controller/EquipmentController.java create mode 100644 backend/src/main/java/com/magistr/app/dto/ClassroomRequest.java create mode 100644 backend/src/main/java/com/magistr/app/dto/ClassroomResponse.java create mode 100644 backend/src/main/java/com/magistr/app/model/Classroom.java create mode 100644 backend/src/main/java/com/magistr/app/model/Equipment.java create mode 100644 backend/src/main/java/com/magistr/app/repository/ClassroomRepository.java create mode 100644 backend/src/main/java/com/magistr/app/repository/EquipmentRepository.java diff --git a/.agent/rules/main.md b/.agent/rules/main.md index f354340..b5d778e 100644 --- a/.agent/rules/main.md +++ b/.agent/rules/main.md @@ -55,7 +55,7 @@ trigger: always_on ### 2. Управление ресурсами и топология - **Управление аудиториями**: - Указание вместимости. - - Указание тэгов оборудования (Проектор, ПК, Лаборатория). + - Привязка доступного оборудования (через сущность Equipments: Проектор, ПК, Лаборатория). - Установка статуса "Не доступно" (блокирует назначение пар в этот период). - **Управление группами**: - Управление списком студентов (и возможность деления на подгруппы). @@ -97,7 +97,8 @@ trigger: always_on ## Основные сущности базы данных (Data Entities) - **Users**: Хранение пользователей и их ролей (Администратор, Преподаватель, Студент) для управления доступом. - **Groups**: Группы студентов, их привязка к формам обучения. (Могут делиться на **подгруппы** для лабораторных и практик). -- **Classrooms**: Аудиторный фонд (название, вместимость, статус доступности, тэги оборудования). +- **Equipments**: Справочник оборудования. +- **Classrooms**: Аудиторный фонд (название, вместимость, статус доступности, привязанный список оборудования Equipments). - **Subjects**: Предметы/Дисциплины (Высшая математика, Физика, Базы данных и т.д.). - **Teacher_Subjects**: Связующая таблица (Many-to-Many), определяющая, какие дисциплины ведет конкретный преподаватель. - **Lesson_Types**: Типы занятий для валидации (Лекция, Практика, Лабораторная работа). diff --git a/backend/src/main/java/com/magistr/app/controller/ClassroomController.java b/backend/src/main/java/com/magistr/app/controller/ClassroomController.java new file mode 100644 index 0000000..8084f41 --- /dev/null +++ b/backend/src/main/java/com/magistr/app/controller/ClassroomController.java @@ -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 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 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 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 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())); + } +} diff --git a/backend/src/main/java/com/magistr/app/controller/EquipmentController.java b/backend/src/main/java/com/magistr/app/controller/EquipmentController.java new file mode 100644 index 0000000..f305efb --- /dev/null +++ b/backend/src/main/java/com/magistr/app/controller/EquipmentController.java @@ -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 getAllEquipments() { + return equipmentRepository.findAll(); + } + + @PostMapping + public ResponseEntity createEquipment(@RequestBody Map 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", "Оборудование удалено")); + } +} diff --git a/backend/src/main/java/com/magistr/app/dto/ClassroomRequest.java b/backend/src/main/java/com/magistr/app/dto/ClassroomRequest.java new file mode 100644 index 0000000..72927e1 --- /dev/null +++ b/backend/src/main/java/com/magistr/app/dto/ClassroomRequest.java @@ -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 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 getEquipmentIds() { + return equipmentIds; + } + + public void setEquipmentIds(List equipmentIds) { + this.equipmentIds = equipmentIds; + } +} diff --git a/backend/src/main/java/com/magistr/app/dto/ClassroomResponse.java b/backend/src/main/java/com/magistr/app/dto/ClassroomResponse.java new file mode 100644 index 0000000..35b46d5 --- /dev/null +++ b/backend/src/main/java/com/magistr/app/dto/ClassroomResponse.java @@ -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 equipments; + + public ClassroomResponse() { + } + + public ClassroomResponse(Long id, String name, Integer capacity, Boolean isAvailable, List 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 getEquipments() { + return equipments; + } + + public void setEquipments(List equipments) { + this.equipments = equipments; + } +} diff --git a/backend/src/main/java/com/magistr/app/model/Classroom.java b/backend/src/main/java/com/magistr/app/model/Classroom.java new file mode 100644 index 0000000..67b2840 --- /dev/null +++ b/backend/src/main/java/com/magistr/app/model/Classroom.java @@ -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 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 getEquipments() { + return equipments; + } + + public void setEquipments(Set equipments) { + this.equipments = equipments; + } +} diff --git a/backend/src/main/java/com/magistr/app/model/Equipment.java b/backend/src/main/java/com/magistr/app/model/Equipment.java new file mode 100644 index 0000000..a550410 --- /dev/null +++ b/backend/src/main/java/com/magistr/app/model/Equipment.java @@ -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; + } +} diff --git a/backend/src/main/java/com/magistr/app/repository/ClassroomRepository.java b/backend/src/main/java/com/magistr/app/repository/ClassroomRepository.java new file mode 100644 index 0000000..aee60f2 --- /dev/null +++ b/backend/src/main/java/com/magistr/app/repository/ClassroomRepository.java @@ -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 { + Optional findByName(String name); +} diff --git a/backend/src/main/java/com/magistr/app/repository/EquipmentRepository.java b/backend/src/main/java/com/magistr/app/repository/EquipmentRepository.java new file mode 100644 index 0000000..8d5a445 --- /dev/null +++ b/backend/src/main/java/com/magistr/app/repository/EquipmentRepository.java @@ -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 { + Optional findByName(String name); +} diff --git a/db/init/init.sql b/db/init/init.sql index 4855da3..a2401e2 100644 --- a/db/init/init.sql +++ b/db/init/init.sql @@ -55,21 +55,49 @@ INSERT INTO lesson_types (name) VALUES ('Лабораторная работа') 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 ( id BIGSERIAL PRIMARY KEY, name VARCHAR(50) UNIQUE NOT NULL, capacity INT NOT NULL, - is_available BOOLEAN DEFAULT TRUE, - hardware_tags VARCHAR(255) -- например: "Проектор, ПК, Лаборатория" + is_available BOOLEAN DEFAULT TRUE ); -INSERT INTO classrooms (name, capacity, hardware_tags) VALUES -('101 Ленинская', 120, 'Проектор, Доска'), -('202 IT Lab', 20, 'ПК, Проектор, Лаборатория'), -('303 Обычная', 30, 'Доска') +INSERT INTO classrooms (name, capacity) VALUES +('101 Ленинская', 120), +('202 IT Lab', 20), +('303 Обычная', 30) 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; + -- ========================================== -- Связи для преподавателей и студентов -- ==========================================