diff --git a/server/pom.xml b/server/pom.xml index 12901ed..69bf55d 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -52,6 +52,11 @@ postgresql runtime + + com.deepoove + poi-tl + 1.12.2 + org.projectlombok lombok @@ -97,6 +102,15 @@ commons-collections4 4.4 + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + 2.17.2 + + + org.springframework.boot + spring-boot-starter-mail + diff --git a/server/src/main/java/ru/mskobaro/tdms/business/entity/Defense.java b/server/src/main/java/ru/mskobaro/tdms/business/entity/Defense.java index 795a1ee..0c54b9b 100644 --- a/server/src/main/java/ru/mskobaro/tdms/business/entity/Defense.java +++ b/server/src/main/java/ru/mskobaro/tdms/business/entity/Defense.java @@ -5,13 +5,29 @@ import lombok.Getter; import lombok.Setter; import java.time.LocalDate; -import java.util.List; +import java.util.Set; @Entity @Table(name = "defense") @Getter @Setter public class Defense { + public enum Status { + NOT_STARTED, + TOPIC_PREPARATION, + TOPIC_CHOOSING, + TASK_CREATING, + TASK_ASSIGNING, + PRE_DIPLOMA_PRACTICE, + TOPIC_FINALIZATION, + MAIN_VKR_WORK, + PRE_DEFENSE, + NORMCONTROL_ANTI_PLAGIARISM, + DEFENSE_PREPARATION, + DEFENSE, + FINISHED, + } + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @@ -21,20 +37,34 @@ public class Defense { name = "defense_commission", joinColumns = @JoinColumn(name = "defense_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "commission_member_data_id", referencedColumnName = "id")) - private List commissionMembers; + private Set commissionMembers; + + @Enumerated(EnumType.STRING) + private Status status; private LocalDate defenseDate; @OneToMany(mappedBy = "defense") - private List groups; - - @ManyToMany - @JoinTable( - name = "defense_best_student_works", - joinColumns = @JoinColumn(name = "defense_id", referencedColumnName = "id"), - inverseJoinColumns = @JoinColumn(name = "student_data_id", referencedColumnName = "id")) - private List bestWorks; + private Set groups; @Embedded private AuditInfo auditInfo; + + @ManyToOne + @JoinColumn(name = "direction_of_preparation_id") + private DirectionOfPreparation directionOfPreparation; + + @ManyToOne + @JoinColumn(name = "responsible_for_antiplagiarism_id") + private Participant responsibleForAntiplagiarism; + + @ManyToOne + @JoinColumn(name = "responsible_for_normcontrol_id") + private Participant responsibleForNormcontrol; + + + @Transient + public boolean isStateShouldBeProcessed(Status status) { + return this.status.ordinal() >= status.ordinal(); + } } diff --git a/server/src/main/java/ru/mskobaro/tdms/business/entity/DirectionOfPreparation.java b/server/src/main/java/ru/mskobaro/tdms/business/entity/DirectionOfPreparation.java index e39483c..bede38b 100644 --- a/server/src/main/java/ru/mskobaro/tdms/business/entity/DirectionOfPreparation.java +++ b/server/src/main/java/ru/mskobaro/tdms/business/entity/DirectionOfPreparation.java @@ -22,6 +22,10 @@ public class DirectionOfPreparation { @OneToMany(mappedBy = "directionOfPreparation") private List diplomaTopic; + @ManyToOne + @JoinColumn(name = "responsible_id") + private TeacherData responsible; + @Embedded private AuditInfo auditInfo; } diff --git a/server/src/main/java/ru/mskobaro/tdms/business/entity/Group.java b/server/src/main/java/ru/mskobaro/tdms/business/entity/Group.java index 4a93a33..97d2f7a 100644 --- a/server/src/main/java/ru/mskobaro/tdms/business/entity/Group.java +++ b/server/src/main/java/ru/mskobaro/tdms/business/entity/Group.java @@ -2,6 +2,7 @@ package ru.mskobaro.tdms.business.entity; import jakarta.persistence.*; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; import lombok.ToString; @@ -15,6 +16,7 @@ import java.util.List; @ToString @Entity @Table(name = "`group`") +@EqualsAndHashCode(of = "id") public class Group { @Id @Column(name = "id") diff --git a/server/src/main/java/ru/mskobaro/tdms/business/entity/MessageTemplate.java b/server/src/main/java/ru/mskobaro/tdms/business/entity/MessageTemplate.java new file mode 100644 index 0000000..abf673c --- /dev/null +++ b/server/src/main/java/ru/mskobaro/tdms/business/entity/MessageTemplate.java @@ -0,0 +1,56 @@ +package ru.mskobaro.tdms.business.entity; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Entity +@Table(name = "message_template") +@Getter +@Setter +@NoArgsConstructor +public class MessageTemplate { + + public enum MessageType { + TOPIC_LIST_PREPARE_START_FOR_TEACHER, + + TOPIC_CHOOSE_FOR_STUDENT, + TOPIC_CHOOSE_FOR_TEACHER, + + TASK_CREATE_FOR_TEACHER, + TASK_ASSIGN_FOR_TEACHER, + + PRE_DIPLOMA_PRACTICE_START_FOR_TEACHER, + PRE_DIPLOMA_PRACTICE_START_FOR_STUDENT, + + TOPIC_FINALIZATION_FOR_TEACHER, + + PRE_DEFENSE_FOR_STUDENT, + + ANTIPLAGIARISM_FOR_STUDENT, + + DEFENSE_PREPARATION_FOR_STUDENT, + + MAIN_VRK_WORK_START, + + NORMCONTROL_AND_ANTIPLAGIARISM, + + DEFENSE, + + DEFENSE_PREPARATION_FOR_TEACHER, + } + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Enumerated(EnumType.STRING) + private MessageType messageType; + + private String subjectTemplate; + private String messageTemplate; + + @Embedded + private AuditInfo auditInfo; +} diff --git a/server/src/main/java/ru/mskobaro/tdms/business/entity/NormcontrolAndAntiplagiarismTask.java b/server/src/main/java/ru/mskobaro/tdms/business/entity/NormcontrolAndAntiplagiarismTask.java new file mode 100644 index 0000000..848eac8 --- /dev/null +++ b/server/src/main/java/ru/mskobaro/tdms/business/entity/NormcontrolAndAntiplagiarismTask.java @@ -0,0 +1,15 @@ +package ru.mskobaro.tdms.business.entity; + +import jakarta.persistence.Entity; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDateTime; + +@Entity +@Getter +@Setter +public class NormcontrolAndAntiplagiarismTask extends Task { + private Boolean normcontrolPassed; + private Long antiplagiarismPercent; +} diff --git a/server/src/main/java/ru/mskobaro/tdms/business/entity/PreDiplomaPracticeTask.java b/server/src/main/java/ru/mskobaro/tdms/business/entity/PreDiplomaPracticeTask.java new file mode 100644 index 0000000..0578888 --- /dev/null +++ b/server/src/main/java/ru/mskobaro/tdms/business/entity/PreDiplomaPracticeTask.java @@ -0,0 +1,14 @@ +package ru.mskobaro.tdms.business.entity; + +import jakarta.persistence.Entity; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDateTime; + +@Entity +@Getter +@Setter +public class PreDiplomaPracticeTask extends Task { + private Long practiceMark; +} diff --git a/server/src/main/java/ru/mskobaro/tdms/business/entity/StudentData.java b/server/src/main/java/ru/mskobaro/tdms/business/entity/StudentData.java index dde5f67..1d4e7c2 100644 --- a/server/src/main/java/ru/mskobaro/tdms/business/entity/StudentData.java +++ b/server/src/main/java/ru/mskobaro/tdms/business/entity/StudentData.java @@ -2,6 +2,7 @@ package ru.mskobaro.tdms.business.entity; import jakarta.persistence.*; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; import lombok.ToString; @@ -13,6 +14,7 @@ import ru.mskobaro.tdms.integration.database.TeacherDataRepository; @ToString(exclude = "group") @Entity @Table(name = "student_data") +@EqualsAndHashCode(of = "id") public class StudentData { @Id @Column(name = "id") @@ -34,9 +36,33 @@ public class StudentData { private Integer protectionOrder; private Integer protectionDay; - private Integer markComment; private Integer markPractice; + @Column(name = "magistracy_wanted") + private Boolean magistracy; + private Boolean electronic; + + @Column(name = "oztiv_mark") + private Integer otziv; + @Column(name = "predefnese_mark") + private Integer preDefenseMark; + + @Column(name = "normal_control") + private Boolean normcontrol; + @Column(name = "anti_plagiarism") + private Integer antiplagiarism; + + @Column(name = "record_book_returned") + private Boolean zachetka; + + @Column(name = "work") + private String work; + + @Column(name = "vnedreniye") + private Boolean vnedreniye; + @Column(name = "diploma_with_honors") + private Boolean otlichiye; + @ManyToOne @JoinColumn(name = "curator_id") private TeacherData curator; @@ -45,6 +71,13 @@ public class StudentData { @JoinColumn(name = "diploma_topic_id") private DiplomaTopic diplomaTopic; + @Column(name = "marks_5") + private Long marks5; + @Column(name = "marks_4") + private Long marks4; + @Column(name = "marks_3") + private Long marks3; + @Embedded private AuditInfo auditInfo; } diff --git a/server/src/main/java/ru/mskobaro/tdms/business/entity/Task.java b/server/src/main/java/ru/mskobaro/tdms/business/entity/Task.java index 102a4f2..65a28f6 100644 --- a/server/src/main/java/ru/mskobaro/tdms/business/entity/Task.java +++ b/server/src/main/java/ru/mskobaro/tdms/business/entity/Task.java @@ -4,38 +4,59 @@ import jakarta.persistence.*; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import org.hibernate.annotations.JdbcTypeCode; -import org.hibernate.type.SqlTypes; -import ru.mskobaro.tdms.business.taskfields.TaskFields; @Entity @Table(name = "task") @NoArgsConstructor @Getter @Setter +@Inheritance(strategy = InheritanceType.SINGLE_TABLE) +@DiscriminatorColumn(name = "task_type", discriminatorType = DiscriminatorType.STRING) public class Task { - public enum Type { - DIPLOMA_TOPIC_AGREEMENT, - } - public enum Status { WAIT_FOR_TOPIC_AGREEMENT, + WAIT_FOR_STUDENT_EDIT, + WAIT_FOR_STUDENT_START, + + WAIT_FOR_TOPIC_PREPARATION, + + WAIT_FOR_STUDENT_PRE_DIPLOMA_PRACTICE_WORK_DONE, + WAIT_FOR_TEACHER_PRE_DIPLOMA_PRACTICE_MARK_SEND, + DONE, + CANCELED, WAIT_FOR_NORMOCONTROL_PASSED, WAIT_FOR_TEACHER_OTZIV, WAIT_FOR_ANTIPL_PASSED, + } + + public enum Type { + TOPIC_AGREEMENT, + TOPIC_PREPARATION, + TASK_CREATING, + TASK_ASSIGN, + PRE_DIPLOMA_PRACTICE, + TOPIC_FINALIZATION, + MAIN_VKR_WORK, + PRE_DEFENSE, + NORMCONTROL_AND_ANTIPLAGIARISM, + DEFENSE_PREPARATION, + DEFENSE, } @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @Enumerated(EnumType.STRING) + private Status status; @Enumerated(EnumType.STRING) private Type type; - @Enumerated(EnumType.STRING) - private Status status; - - @JdbcTypeCode(SqlTypes.JSON) - private TaskFields fields; + @ManyToOne + @JoinColumn(name = "defense_id") + private Defense defense; @Embedded private AuditInfo auditInfo; + + private Long makerParticId; + private Long checkerParticId; } diff --git a/server/src/main/java/ru/mskobaro/tdms/business/entity/TopicAgreementTask.java b/server/src/main/java/ru/mskobaro/tdms/business/entity/TopicAgreementTask.java new file mode 100644 index 0000000..b2fb1ac --- /dev/null +++ b/server/src/main/java/ru/mskobaro/tdms/business/entity/TopicAgreementTask.java @@ -0,0 +1,16 @@ +package ru.mskobaro.tdms.business.entity; + +import jakarta.persistence.Entity; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDateTime; + +@Entity +@Getter +@Setter +public class TopicAgreementTask extends Task { + private Long diplomaTopicId; + private String diplomaTopicName; + private LocalDateTime approvedAt; +} diff --git a/server/src/main/java/ru/mskobaro/tdms/business/exception/AccessDeniedException.java b/server/src/main/java/ru/mskobaro/tdms/business/exception/AccessDeniedException.java index d68250c..64de4da 100644 --- a/server/src/main/java/ru/mskobaro/tdms/business/exception/AccessDeniedException.java +++ b/server/src/main/java/ru/mskobaro/tdms/business/exception/AccessDeniedException.java @@ -1,6 +1,6 @@ package ru.mskobaro.tdms.business.exception; -import ru.mskobaro.tdms.presentation.controller.payload.ErrorDTO; +import ru.mskobaro.tdms.integration.controller.payload.ErrorDTO; public class AccessDeniedException extends BusinessException { public AccessDeniedException() { diff --git a/server/src/main/java/ru/mskobaro/tdms/business/exception/BusinessException.java b/server/src/main/java/ru/mskobaro/tdms/business/exception/BusinessException.java index de2b01c..fdd89ec 100644 --- a/server/src/main/java/ru/mskobaro/tdms/business/exception/BusinessException.java +++ b/server/src/main/java/ru/mskobaro/tdms/business/exception/BusinessException.java @@ -1,12 +1,16 @@ package ru.mskobaro.tdms.business.exception; -import ru.mskobaro.tdms.presentation.controller.payload.ErrorDTO; +import ru.mskobaro.tdms.integration.controller.payload.ErrorDTO; public class BusinessException extends RuntimeException { public BusinessException(String message) { super(message); } + public BusinessException(String message, Exception cause) { + super(message, cause); + } + public ErrorDTO.ErrorCode getErrorCode() { return ErrorDTO.ErrorCode.BUSINESS_ERROR; } diff --git a/server/src/main/java/ru/mskobaro/tdms/business/exception/NotFoundException.java b/server/src/main/java/ru/mskobaro/tdms/business/exception/NotFoundException.java index afd2631..ce41aec 100644 --- a/server/src/main/java/ru/mskobaro/tdms/business/exception/NotFoundException.java +++ b/server/src/main/java/ru/mskobaro/tdms/business/exception/NotFoundException.java @@ -1,6 +1,6 @@ package ru.mskobaro.tdms.business.exception; -import ru.mskobaro.tdms.presentation.controller.payload.ErrorDTO; +import ru.mskobaro.tdms.integration.controller.payload.ErrorDTO; public class NotFoundException extends BusinessException { public NotFoundException(Class entityClass, Object id) { diff --git a/server/src/main/java/ru/mskobaro/tdms/business/service/DefenceService.java b/server/src/main/java/ru/mskobaro/tdms/business/service/DefenceService.java deleted file mode 100644 index 1510eca..0000000 --- a/server/src/main/java/ru/mskobaro/tdms/business/service/DefenceService.java +++ /dev/null @@ -1,20 +0,0 @@ -package ru.mskobaro.tdms.business.service; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import ru.mskobaro.tdms.integration.database.DefenceRepository; -import ru.mskobaro.tdms.presentation.controller.payload.DefenceDTO; - -import java.util.List; - -@Service -@Transactional -public class DefenceService { - @Autowired - private DefenceRepository defenceRepository; - - public List getAllDefences() { - return defenceRepository.findAll().stream().map(DefenceDTO::from).toList(); - } -} diff --git a/server/src/main/java/ru/mskobaro/tdms/business/service/DefenseService.java b/server/src/main/java/ru/mskobaro/tdms/business/service/DefenseService.java new file mode 100644 index 0000000..f54391f --- /dev/null +++ b/server/src/main/java/ru/mskobaro/tdms/business/service/DefenseService.java @@ -0,0 +1,338 @@ +package ru.mskobaro.tdms.business.service; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import org.hibernate.Hibernate; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.mskobaro.tdms.business.entity.*; +import ru.mskobaro.tdms.business.exception.BusinessException; +import ru.mskobaro.tdms.integration.controller.DefenseController; +import ru.mskobaro.tdms.integration.controller.payload.CommissionMemberDTO; +import ru.mskobaro.tdms.integration.controller.payload.DefenseDTO; +import ru.mskobaro.tdms.integration.controller.payload.GroupDTO; +import ru.mskobaro.tdms.integration.database.*; + +import java.time.LocalDate; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Service +@Transactional +public class DefenseService { + @Autowired + private DefenseRepository defenseRepository; + @Autowired + private CommissionMemberRepository commissionMemberRepository; + @Autowired + private GroupRepository groupRepository; + @PersistenceContext + private EntityManager entityManager; + @Autowired + private TaskService taskService; + @Autowired + private ParticipantRepository participantRepository; + @Autowired + private TeacherDataRepository teacherDataRepository; + @Autowired + private PreparationDirectionRepository preparationDirectionRepository; + + public List findAll() { + List defenses = entityManager.createQuery("select d from Defense d left join fetch d.commissionMembers", Defense.class) + .getResultList(); + + defenses.forEach(defense -> defense.getGroups().forEach(group -> Hibernate.initialize(group.getStudents()))); + return defenses; + } + + public Defense findById(Long id) { + return entityManager.createQuery( + "select d from Defense d " + + "left join fetch d.commissionMembers cm " + + "left join fetch cm.participant " + + "left join fetch d.groups g " + + "left join fetch g.students " + + "where d.id = :id " + , Defense.class) + .setParameter("id", id) + .getSingleResult(); + } + + public void save(DefenseDTO defenseDTO) { + boolean editMode = defenseDTO.getId() != null; + + Defense defense; + if (editMode) { + defense = defenseRepository.findByIdThrow(defenseDTO.getId()); + } else { + defense = new Defense(); + } + + List members = commissionMemberRepository.findAllById( + defenseDTO.getCommissionMembers().stream().map(CommissionMemberDTO::getId).collect(Collectors.toList()) + ); + defense.setCommissionMembers(new HashSet<>(members)); + + if (defenseDTO.getResponsibleForNorm() != null && defenseDTO.getResponsibleForNorm().getId() != null) { + Participant norm = participantRepository.findByIdThrow(defenseDTO.getResponsibleForNorm().getId()); + defense.setResponsibleForNormcontrol(norm); + } + + if (defenseDTO.getResponsibleForAntipl() != null && defenseDTO.getResponsibleForAntipl().getId() != null) { + Participant antipl = participantRepository.findByIdThrow(defenseDTO.getResponsibleForAntipl().getId()); + defense.setResponsibleForAntiplagiarism(antipl); + } + + if (defenseDTO.getPreparationDirection() != null && defenseDTO.getPreparationDirection().getId() != null) { + DirectionOfPreparation dirOfPrep = preparationDirectionRepository.findByIdThrow(defenseDTO.getPreparationDirection().getId()); + defense.setDirectionOfPreparation(dirOfPrep); + } + + if (!editMode) { + defense.setStatus(Defense.Status.NOT_STARTED); + } + + if (defenseDTO.getDefenseDate() != null) { + defense.setDefenseDate(defenseDTO.getDefenseDate()); + } + + defense = defenseRepository.save(defense); + + List groups = groupRepository.findAllById( + defenseDTO.getGroups().stream().map(GroupDTO::getId).collect(Collectors.toList())); + for (Group g : groups) { + g.setDefense(defense); + } + } + + public void handleNextStateRequest(Long id) { + Defense defense = defenseRepository.findByIdThrow(id); + toNextState(defense); + processState(defense, null); + } + + private void processState(Defense defense, Defense.Status status) { + List students = defense.getGroups().stream() + .flatMap(g -> g.getStudents().stream()) + .toList(); + + List curators = students.stream() + .map(StudentData::getCurator) + .toList(); + + if (status == null) { + status = defense.getStatus(); + } + + if (status == Defense.Status.TOPIC_PREPARATION) { + List all = teacherDataRepository.findAll(); + taskService.checkAndCreateTopicPreparationTasksAndSendEmail(all, defense); + } else if (status == Defense.Status.TOPIC_CHOOSING) { + taskService.checkAndCreateTopicChoosingTasksAndSendEmail(students, defense); + } else if (status == Defense.Status.TASK_CREATING) { + taskService.checkAndCreateTaskCreatingTask(curators, defense); + } else if (status == Defense.Status.TASK_ASSIGNING) { + taskService.checkAndCreateTaskAssignTasks(curators, defense); + } else if (status == Defense.Status.PRE_DIPLOMA_PRACTICE) { + taskService.checkAndCreatePreDiplomaPracticeTask(students, defense); + } else if (status == Defense.Status.TOPIC_FINALIZATION) { + taskService.checkAndCreateTopicFinalizationTask(defense, curators); + } else if (status == Defense.Status.MAIN_VKR_WORK) { + taskService.checkAndCreateMainVkrWorkTask(defense, students); + } else if (status == Defense.Status.PRE_DEFENSE) { + taskService.checkAndCreatePreDefenseTask(defense, students); + } else if (status == Defense.Status.NORMCONTROL_ANTI_PLAGIARISM) { + taskService.checkAndCreateNormcontrolAndAntiplagiarismTask(defense, students); + } else if (status == Defense.Status.DEFENSE_PREPARATION) + taskService.checkAndCreateDefensePreparationTasks(defense, students); + else if (status == Defense.Status.DEFENSE) { + taskService.checkAndCreateDefenseTask(defense, students); + } else if (status == Defense.Status.FINISHED) { + taskService.clearAllTasksWithDefense(defense); + } + } + + private void toNextState(Defense defense) { + switch (defense.getStatus()) { + case NOT_STARTED -> + defense.setStatus(Defense.Status.TOPIC_PREPARATION); + case TOPIC_PREPARATION -> + defense.setStatus(Defense.Status.TOPIC_CHOOSING); + case TOPIC_CHOOSING -> + defense.setStatus(Defense.Status.TASK_CREATING); + case TASK_CREATING -> + defense.setStatus(Defense.Status.TASK_ASSIGNING); + case TASK_ASSIGNING -> + defense.setStatus(Defense.Status.PRE_DIPLOMA_PRACTICE); + case PRE_DIPLOMA_PRACTICE -> + defense.setStatus(Defense.Status.TOPIC_FINALIZATION); + case TOPIC_FINALIZATION -> + defense.setStatus(Defense.Status.MAIN_VKR_WORK); + case MAIN_VKR_WORK -> + defense.setStatus(Defense.Status.PRE_DEFENSE); + case PRE_DEFENSE -> + defense.setStatus(Defense.Status.NORMCONTROL_ANTI_PLAGIARISM); + case NORMCONTROL_ANTI_PLAGIARISM -> + defense.setStatus(Defense.Status.DEFENSE_PREPARATION); + case DEFENSE_PREPARATION -> + defense.setStatus(Defense.Status.DEFENSE); + case DEFENSE -> + defense.setStatus(Defense.Status.FINISHED); + case FINISHED -> + throw new BusinessException("Защита окончена"); + } + } + + public DefenseController.DefenseTableData tableDataByDefenseId(Long defenseId) { + List allDefenseStudents = entityManager.createQuery(""" + select sd from StudentData sd + where sd.group.defense.id = :defenseId + """, StudentData.class + ).setParameter("defenseId", defenseId) + .getResultList(); + + List groupsData = new ArrayList<>(); + for (StudentData student : allDefenseStudents) { + DefenseController.DefenseTableDataGroup groupData = new DefenseController.DefenseTableDataGroup(); + groupData.setGroupId(student.getGroup().getId()); + groupData.setGroupName(student.getGroup().getName()); + groupData.setStudId(student.getId()); + groupData.setFio(student.getParticipant().getFullName()); + if (student.getMarkPractice() != null) { + groupData.setPdpMark(student.getMarkPractice()); + } + if (student.getDiplomaTopic() != null) { + groupData.setTopic(student.getDiplomaTopic().getName()); + } + if (student.getCurator() != null) { + groupData.setTeacherFio(student.getCurator().getParticipant().getFullName()); + } + if (student.getProtectionDay() != null) { + groupData.setDayOfProt(student.getProtectionDay()); + } + if (student.getProtectionDay() != null) { + groupData.setPlaceOfProt(student.getProtectionOrder()); + } + groupData.setMagistracy(student.getMagistracy()); + groupData.setElectronic(student.getElectronic()); + groupData.setOtziv(student.getOtziv()); + groupData.setDpedefenseMark(student.getPreDefenseMark()); + groupData.setNormcontrol(student.getNormcontrol()); + groupData.setAntipl(student.getAntiplagiarism()); + groupData.setZachetka(student.getZachetka()); + groupData.setWork(student.getWork()); + groupData.setVnedreniye(student.getVnedreniye()); + groupData.setOtlichiye(student.getOtlichiye()); + groupsData.add(groupData); + } + + DefenseController.DefenseTableData dto = new DefenseController.DefenseTableData(); + dto.setDefenseId(defenseId); + dto.setGroups(groupsData); + return dto; + } + + public void tableDataSave(DefenseController.DefenseTableData defenseTableData) { + List groups = defenseTableData.getGroups(); + for (DefenseController.DefenseTableDataGroup groupData : groups) { + StudentData student = entityManager.createQuery(""" + select sd from StudentData sd + where sd.group.defense.id = :defenseId + and sd.id = :id + """, StudentData.class + ).setParameter("defenseId", defenseTableData.getDefenseId()) + .setParameter("id", groupData.getStudId()) + .getSingleResult(); + + + if (groupData.getPdpMark() != null) { + student.setMarkPractice(groupData.getPdpMark()); + } + + if (groupData.getMagistracy() != null) { + student.setMagistracy(groupData.getMagistracy()); + } + + if (groupData.getElectronic() != null) { + student.setElectronic(groupData.getElectronic()); + } + + if (groupData.getOtziv() != null) { + student.setOtziv(groupData.getOtziv()); + } + + if (groupData.getDpedefenseMark() != null) { + student.setPreDefenseMark(groupData.getDpedefenseMark()); + } + + if (groupData.getZachetka() != null) { + student.setZachetka(groupData.getZachetka()); + } + + if (groupData.getWork() != null) { + student.setWork(groupData.getWork()); + } + + if (groupData.getVnedreniye() != null) { + student.setVnedreniye(groupData.getVnedreniye()); + } + + if (groupData.getOtlichiye() != null) { + student.setOtlichiye(groupData.getOtlichiye()); + } + } + } + + public void recreateProtectionOrder(Long defenseId) { + Defense defense = defenseRepository.findByIdThrow(defenseId); + + List students = defense.getGroups().stream() + .map(Group::getStudents) + .flatMap(List::stream) + .sorted(Comparator.comparing(student -> { + if (student.getCurator() != null && student.getCurator().getParticipant() != null) + return student.getCurator().getParticipant().getFullName(); + return null; + }, Comparator.nullsLast(Comparator.naturalOrder()) + )) + .toList(); + + final int maxInDay = 16; + int currentDay = 1; + int currentOrder = 1; + for (StudentData student : students) { + if (currentOrder > maxInDay) { + currentOrder = 1; + currentDay += 1; + } + + student.setProtectionDay(currentDay); + student.setProtectionOrder(currentOrder++); + } + } + + public void updateProtOrder(DefenseController.ProtOrDTO dto) { + Defense defense = defenseRepository.findByIdThrow(dto.getDefenseId()); + Map studentsById = defense.getGroups().stream() + .map(Group::getStudents) + .flatMap(Collection::stream) + .collect(Collectors.toMap(StudentData::getId, Function.identity())); + + dto.getDays().forEach(day -> day.getStuds().forEach(student -> { + StudentData studentData = studentsById.get(student.getId()); + if (studentData != null) { + studentData.setProtectionDay(day.getNumber()); + studentData.setProtectionOrder(student.getOrder() + 1); + } + })); + dto.getUnassigned().forEach(unassigned -> { + StudentData studentData = studentsById.get(unassigned.getId()); + if (studentData != null) { + studentData.setProtectionDay(null); + studentData.setProtectionOrder(null); + } + }); + } +} diff --git a/server/src/main/java/ru/mskobaro/tdms/business/service/DiplomaTopicService.java b/server/src/main/java/ru/mskobaro/tdms/business/service/DiplomaTopicService.java index af341e7..d6b916c 100644 --- a/server/src/main/java/ru/mskobaro/tdms/business/service/DiplomaTopicService.java +++ b/server/src/main/java/ru/mskobaro/tdms/business/service/DiplomaTopicService.java @@ -1,5 +1,7 @@ package ru.mskobaro.tdms.business.service; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; import jakarta.transaction.Transactional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -9,7 +11,7 @@ import ru.mskobaro.tdms.business.entity.TeacherData; import ru.mskobaro.tdms.integration.database.DiplomaTopicRepository; import ru.mskobaro.tdms.integration.database.PreparationDirectionRepository; import ru.mskobaro.tdms.integration.database.TeacherDataRepository; -import ru.mskobaro.tdms.presentation.controller.payload.DiplomaTopicDTO; +import ru.mskobaro.tdms.integration.controller.payload.DiplomaTopicDTO; import java.util.List; @@ -22,6 +24,8 @@ public class DiplomaTopicService { private TeacherDataRepository teacherDataRepository; @Autowired private PreparationDirectionRepository preparationDirectionRepository; + @PersistenceContext + private EntityManager entityManager; public List findAll() { return diplomaTopicRepository.findAll(); @@ -49,7 +53,16 @@ public class DiplomaTopicService { diplomaTopicRepository.save(diplomaTopic); } - public List findAllForStudent(Long studentId) { - return diplomaTopicRepository.findAllForStudentId(studentId); + public List findAllForStudentByParticId(Long particId) { + return diplomaTopicRepository.findAllForStudentByParticId(particId); + } + + public List findByTeacherParticIdAndDirPrep(Long particId, Long dirPrepId) { + return entityManager.createQuery( + "select dt from DiplomaTopic dt where dt.directionOfPreparation.id = :dirPrepId and dt.teacher.participant.id = :particId", + DiplomaTopic.class + ).setParameter("dirPrepId", dirPrepId) + .setParameter("particId", particId) + .getResultList(); } } diff --git a/server/src/main/java/ru/mskobaro/tdms/business/service/DocumentsService.java b/server/src/main/java/ru/mskobaro/tdms/business/service/DocumentsService.java new file mode 100644 index 0000000..9ecf600 --- /dev/null +++ b/server/src/main/java/ru/mskobaro/tdms/business/service/DocumentsService.java @@ -0,0 +1,272 @@ +package ru.mskobaro.tdms.business.service; + +import com.deepoove.poi.XWPFTemplate; +import com.deepoove.poi.data.*; +import com.deepoove.poi.data.style.CellStyle; +import com.deepoove.poi.data.style.ParagraphStyle; +import com.deepoove.poi.data.style.Style; +import com.deepoove.poi.data.style.TableStyle; +import com.deepoove.poi.util.UnitUtils; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import lombok.Getter; +import lombok.Setter; +import lombok.SneakyThrows; +import org.apache.commons.lang3.StringUtils; +import org.apache.poi.xwpf.usermodel.ParagraphAlignment; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.mskobaro.tdms.business.entity.*; +import ru.mskobaro.tdms.business.exception.BusinessException; +import ru.mskobaro.tdms.integration.database.DefenseRepository; + +import java.io.ByteArrayOutputStream; +import java.time.LocalDate; +import java.util.*; + +@Service +@Transactional +public class DocumentsService { + @Autowired + private DefenseRepository defenseRepository; + @PersistenceContext + private EntityManager entityManager; + + public byte[] getQuestionsDocument(Long defenseId, String studyForm) { + @Getter + @Setter + class Data { + String day; + String month; + String year; + String studFio; + String prepDir; + String form; + String gekFio; + } + + List data = new ArrayList<>(); + Defense defense = defenseRepository.findByIdThrow(defenseId); + LocalDate defenseDate = defense.getDefenseDate(); + String defDay; + String defMonth; + String defYear; + if (defenseDate != null) { + defDay = Integer.toString(defenseDate.getDayOfMonth()); + defMonth = Integer.toString(defenseDate.getMonthValue()); + defYear = Integer.toString(defenseDate.getYear() - 2000); + } else { + defDay = ""; + defMonth = ""; + defYear = ""; + } + + List gek = defense.getCommissionMembers().stream().map(CommissionMemberData::getParticipant).toList(); + List students = defense.getGroups().stream().map(Group::getStudents).flatMap(List::stream).toList(); + for (StudentData stud : students) { + String day = null; + String month = null; + if (StringUtils.isNotBlank(defDay) && stud.getProtectionDay() != null) { + day = Integer.toString(Integer.parseInt(defDay) + stud.getProtectionDay() - 1); + } + if (StringUtils.isNotBlank(defMonth)) { + month = getMonthNameByValue(Integer.parseInt(defMonth)); + } + for (Participant g : gek) { + Data d = new Data(); + d.day = day; + d.month = month; + d.year = defYear; + d.studFio = stud.getParticipant().getShortName(); + d.prepDir = stud.getGroup().getDirectionOfPreparation().getCode(); + d.form = studyForm; + d.gekFio = g.getFullName(); + data.add(d); + } + } + + return renderFileWith(Map.of("data", data), "doc_templates/questions_template.docx"); + } + + public byte[] getTopicList(Long id) { + Defense defense = defenseRepository.findByIdThrow(id); + DirectionOfPreparation prep = defense.getDirectionOfPreparation(); + List topics = entityManager.createQuery("select dt from DiplomaTopic dt where dt.directionOfPreparation.id = :id", DiplomaTopic.class) + .setParameter("id", prep.getId()) + .getResultList(); + + String prepCode = prep.getCode(); + String prepName = prep.getName(); + NumberingRenderData diplomaTopics = new NumberingRenderData( + NumberingFormat.DECIMAL, + topics.stream() + .map(DiplomaTopic::getName) + .map(n -> n + ".") + .map(n -> { + ParagraphRenderData paragraphRenderData = new ParagraphRenderData(); + paragraphRenderData.setContents(Collections.singletonList(new TextRenderData(n))); + return paragraphRenderData; + }).toList() + ); + + return renderFileWith(Map.of( + "prep_code", prepCode, + "prep_name", prepName, + "diploma_topics", diplomaTopics + ), "doc_templates/diploma_topic_list_template.docx"); + } + + public byte[] getStudList(Long id, String stepName) { + Defense defense = defenseRepository.findByIdThrow(id); + + Map> studentByProtDay = new HashMap<>(); + defense.getGroups().stream().map(Group::getStudents).flatMap(List::stream).forEach(student -> { + Integer protectionDay = student.getProtectionDay(); + List array = studentByProtDay.computeIfAbsent(protectionDay, k -> new ArrayList<>()); + array.add(student); + }); + + List days = new ArrayList<>(); + LocalDate defenseDate = defense.getDefenseDate(); + int dayOfMonth = defenseDate.getDayOfMonth(); + int month = defenseDate.getMonthValue(); + int year = defenseDate.getYear(); + DirectionOfPreparation directionOfPreparation = defense.getDirectionOfPreparation(); + String prepCode = directionOfPreparation.getCode(); + String prepName = directionOfPreparation.getName(); + + for (Map.Entry> entry : studentByProtDay.entrySet()) { + if (entry.getKey() == null) continue; + int protDay = entry.getKey() + dayOfMonth - 1; + ProtDay day = new ProtDay(); + day.setDay(Integer.toString(protDay)); + day.setMonth(getMonthNameByValue(month)); + day.setYear(Integer.toString(year)); + day.setStepName(stepName); + day.setPrepCode(prepCode); + day.setPrepName(prepName); + makeTable(day, entry); + days.add(day); + } + + return renderFileWith(Map.of("prot_day", days), "doc_templates/student_list_template.docx"); + } + + private void makeTable(ProtDay day, Map.Entry> entry) { + TableStyle tableStyle = new TableStyle(); + tableStyle.setWidth("100%"); + tableStyle.setColWidths(new int[]{ + UnitUtils.cm2Twips(0.8), UnitUtils.cm2Twips(4.7), UnitUtils.cm2Twips(3.25), UnitUtils.cm2Twips(9.5), + UnitUtils.cm2Twips(1.75), UnitUtils.cm2Twips(2d), UnitUtils.cm2Twips(1d), UnitUtils.cm2Twips(1.5), + UnitUtils.cm2Twips(1.25), UnitUtils.cm2Twips(1.25), UnitUtils.cm2Twips(1.50) + }); + + List rows = new ArrayList<>(); + rows.add(Rows.of( + "№", "ФИО\nстудента", "ФИО\nруководителя", "Тема", "Оценок\n5/4/3", "Коммен-\nтарий", + "Оц. рук.", "Оц. чл. ГЭК", "ИТОГ", "Дипл. с отл.", "Реком. магист." + ).center().textBold().textFontSize(10).create()); + int i = 1; + for (StudentData stud : entry.getValue()) { + Rows.RowBuilder row = Rows.of( + i++ + ".", + stud.getParticipant().getFullName(), + (stud.getCurator() == null ? "" : stud.getCurator().getParticipant().getShortName()), + (stud.getDiplomaTopic() == null ? "" : stud.getDiplomaTopic().getName()), + ("%s/%s/%s".formatted(stud.getMarks5() == null ? "-" : stud.getMarks5(), stud.getMarks4() == null ? "-" : stud.getMarks4(), stud.getMarks3() == null ? "-" : stud.getMarks3())), + (stud.getVnedreniye() ? "Заявка, акт о внедр." : ""), + (stud.getOtziv() == null ? "" : Integer.toString(stud.getOtziv())) + , "", "", + stud.getOtlichiye() ? "да" : "" + ).textFontSize(10).center(); + row.addCell(Cells.of(stud.getMagistracy() ? "да" : "").center().create()); + rows.add(row.create()); + } + + day.setProtDayTable(Tables.of(rows.toArray(RowRenderData[]::new)).create()); + day.getProtDayTable().setTableStyle(tableStyle); + } + + @SneakyThrows + private byte[] renderFileWith(Map tags, String filename) { + try { + XWPFTemplate template = XWPFTemplate + .compile(getClass().getClassLoader().getResource(filename).openStream()) + .render(tags); + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + template.write(stream); + return stream.toByteArray(); + } catch ( + Exception e) { + throw new BusinessException("Ошибка формирования документа", e); + } + } + + private String getMonthNameByValue(int month) { + return switch (month) { + case 1 -> "Января"; + case 2 -> "Февраля"; + case 3 -> "Марта"; + case 4 -> "Апреля"; + case 5 -> "Мая"; + case 6 -> "Июня"; + case 7 -> "Июля"; + case 8 -> "Августа"; + case 9 -> "Сентября"; + case 10 -> "Октября"; + case 11 -> "Ноября"; + case 12 -> "Декабря"; + default -> String.valueOf(month); + }; + } + + private CellRenderData createCell(String text) { + return createCell(text, null); + } + + private CellRenderData createCell(String text, CellStyle cellStyle) { + CellRenderData cellRenderData = new CellRenderData(); + if (cellStyle != null) { + cellRenderData.setCellStyle(cellStyle); + } + ParagraphRenderData paragraph = new ParagraphRenderData(); + paragraph.setContents(Collections.singletonList(new TextRenderData(text))); + cellRenderData.setParagraphs(Collections.singletonList(paragraph)); + return cellRenderData; + } + + private CellStyle boldCenter() { + Style style = new Style(); + style.setBold(true); + ParagraphStyle paragraphStyle = new ParagraphStyle(); + paragraphStyle.setAlign(ParagraphAlignment.BOTH); + paragraphStyle.setGlyphStyle(style); + CellStyle boldStyle = new CellStyle(); + boldStyle.setDefaultParagraphStyle(paragraphStyle); + return boldStyle; + } + + private CellStyle normalCenter() { + Style style = new Style(); + style.setBold(false); + ParagraphStyle paragraphStyle = new ParagraphStyle(); + paragraphStyle.setAlign(ParagraphAlignment.BOTH); + paragraphStyle.setGlyphStyle(style); + CellStyle boldStyle = new CellStyle(); + boldStyle.setDefaultParagraphStyle(paragraphStyle); + return boldStyle; + } + + @Getter + @Setter + class ProtDay { + private String day; + private String month; + private String year; + private String stepName; + private String prepCode; + private String prepName; + private TableRenderData protDayTable = new TableRenderData(); + } +} diff --git a/server/src/main/java/ru/mskobaro/tdms/business/service/EmailService.java b/server/src/main/java/ru/mskobaro/tdms/business/service/EmailService.java new file mode 100644 index 0000000..a5f5683 --- /dev/null +++ b/server/src/main/java/ru/mskobaro/tdms/business/service/EmailService.java @@ -0,0 +1,178 @@ +package ru.mskobaro.tdms.business.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.stereotype.Service; +import ru.mskobaro.tdms.business.entity.MessageTemplate; +import ru.mskobaro.tdms.business.entity.StudentData; +import ru.mskobaro.tdms.business.entity.TeacherData; +import ru.mskobaro.tdms.integration.database.MessageTemplateRepository; + +@Service +public class EmailService { + @Autowired + private JavaMailSender mailSender; + @Autowired + private MessageTemplateRepository messageTemplateRepository; + @Value("${application.smtp.message-from}") + private String from; + + public void sendEmail(String to, String subject, String text) { + SimpleMailMessage message = new SimpleMailMessage(); + message.setFrom(from); + message.setTo(to); + message.setSubject(subject); + message.setText(text); + mailSender.send(message); + } + + public void sendTopicPreparationStartMessage(TeacherData curator) { + MessageTemplate messageTemplate = messageTemplateRepository.findByMessageType( + MessageTemplate.MessageType.TOPIC_LIST_PREPARE_START_FOR_TEACHER + ); + + String message = messageTemplate.getMessageTemplate(); + String subject = messageTemplate.getSubjectTemplate(); + message = message.replaceAll("\\$\\$\\{ФИО_НАУЧ_РУК}", curator.getParticipant().getFullName()); + subject = subject.replaceAll("\\$\\$\\{ФИО_НАУЧ_РУК}", curator.getParticipant().getFullName()); + sendEmail(curator.getParticipant().getEmail(), subject, message); + } + + public void sendTopicChoosingStartMessage(TeacherData curator, StudentData student) { + MessageTemplate messageTemplate = messageTemplateRepository.findByMessageType( + MessageTemplate.MessageType.TOPIC_CHOOSE_FOR_TEACHER + ); + String message = messageTemplate.getMessageTemplate(); + String subject = messageTemplate.getSubjectTemplate(); + message = message.replaceAll("\\$\\$\\{ФИО_НАУЧ_РУК}", curator.getParticipant().getFullName()); + subject = subject.replaceAll("\\$\\$\\{ФИО_НАУЧ_РУК}", curator.getParticipant().getFullName()); + sendEmail(curator.getParticipant().getEmail(), subject, message); + + messageTemplate = messageTemplateRepository.findByMessageType( + MessageTemplate.MessageType.TOPIC_CHOOSE_FOR_STUDENT + ); + message = messageTemplate.getMessageTemplate(); + subject = messageTemplate.getSubjectTemplate(); + message = message.replaceAll("\\$\\$\\{ФИО_СТУД}", student.getParticipant().getFullName()); + subject = subject.replaceAll("\\$\\$\\{ФИО_СТУД}", student.getParticipant().getFullName()); + sendEmail(student.getParticipant().getEmail(), subject, message); + } + + public void sendTaskCreatingMessage(TeacherData curator) { + MessageTemplate messageTemplate = messageTemplateRepository.findByMessageType( + MessageTemplate.MessageType.TASK_CREATE_FOR_TEACHER + ); + + String message = messageTemplate.getMessageTemplate(); + String subject = messageTemplate.getSubjectTemplate(); + message = message.replaceAll("\\$\\$\\{ФИО_НАУЧ_РУК}", curator.getParticipant().getFullName()); + subject = subject.replaceAll("\\$\\$\\{ФИО_НАУЧ_РУК}", curator.getParticipant().getFullName()); + sendEmail(curator.getParticipant().getEmail(), subject, message); + } + + public void sendTaskAssigningMessage(TeacherData curator) { + MessageTemplate messageTemplate = messageTemplateRepository.findByMessageType( + MessageTemplate.MessageType.TASK_ASSIGN_FOR_TEACHER + ); + + String message = messageTemplate.getMessageTemplate(); + String subject = messageTemplate.getSubjectTemplate(); + message = message.replaceAll("\\$\\$\\{ФИО_НАУЧ_РУК}", curator.getParticipant().getFullName()); + subject = subject.replaceAll("\\$\\$\\{ФИО_НАУЧ_РУК}", curator.getParticipant().getFullName()); + sendEmail(curator.getParticipant().getEmail(), subject, message); + } + + public void sendPreDiplomaPracticeMessage(TeacherData curator, StudentData student) { + MessageTemplate messageTemplate = messageTemplateRepository.findByMessageType( + MessageTemplate.MessageType.PRE_DIPLOMA_PRACTICE_START_FOR_TEACHER + ); + String message = messageTemplate.getMessageTemplate(); + String subject = messageTemplate.getSubjectTemplate(); + message = message.replaceAll("\\$\\$\\{ФИО_НАУЧ_РУК}", curator.getParticipant().getFullName()); + subject = subject.replaceAll("\\$\\$\\{ФИО_НАУЧ_РУК}", curator.getParticipant().getFullName()); + sendEmail(curator.getParticipant().getEmail(), subject, message); + + messageTemplate = messageTemplateRepository.findByMessageType( + MessageTemplate.MessageType.PRE_DIPLOMA_PRACTICE_START_FOR_STUDENT + ); + message = messageTemplate.getMessageTemplate(); + subject = messageTemplate.getSubjectTemplate(); + message = message.replaceAll("\\$\\$\\{ФИО_СТУД}", student.getParticipant().getFullName()); + subject = subject.replaceAll("\\$\\$\\{ФИО_СТУД}", student.getParticipant().getFullName()); + sendEmail(student.getParticipant().getEmail(), subject, message); + } + + public void sendTopicFinalizationMessage(TeacherData curator) { + MessageTemplate messageTemplate = messageTemplateRepository.findByMessageType( + MessageTemplate.MessageType.TOPIC_FINALIZATION_FOR_TEACHER + ); + + String message = messageTemplate.getMessageTemplate(); + String subject = messageTemplate.getSubjectTemplate(); + message = message.replaceAll("\\$\\$\\{ФИО_НАУЧ_РУК}", curator.getParticipant().getFullName()); + subject = subject.replaceAll("\\$\\$\\{ФИО_НАУЧ_РУК}", curator.getParticipant().getFullName()); + sendEmail(curator.getParticipant().getEmail(), subject, message); + } + + public void sendMainVrkWorkMessage(StudentData student) { + MessageTemplate messageTemplate = messageTemplateRepository.findByMessageType( + MessageTemplate.MessageType.MAIN_VRK_WORK_START + ); + String message = messageTemplate.getMessageTemplate(); + String subject = messageTemplate.getSubjectTemplate(); + message = message.replaceAll("\\$\\$\\{ФИО_СТУД}", student.getParticipant().getFullName()); + subject = subject.replaceAll("\\$\\$\\{ФИО_СТУД}", student.getParticipant().getFullName()); + sendEmail(student.getParticipant().getEmail(), subject, message); + } + + public void sendPreDefenseStartMessage(StudentData student) { + MessageTemplate messageTemplate = messageTemplateRepository.findByMessageType( + MessageTemplate.MessageType.PRE_DEFENSE_FOR_STUDENT + ); + String message = messageTemplate.getMessageTemplate(); + String subject = messageTemplate.getSubjectTemplate(); + message = message.replaceAll("\\$\\$\\{ФИО_СТУД}", student.getParticipant().getFullName()); + subject = subject.replaceAll("\\$\\$\\{ФИО_СТУД}", student.getParticipant().getFullName()); + sendEmail(student.getParticipant().getEmail(), subject, message); + } + + public void sendNormcontrolAndAntiplagiarismMessage(StudentData student) { + MessageTemplate messageTemplate = messageTemplateRepository.findByMessageType( + MessageTemplate.MessageType.NORMCONTROL_AND_ANTIPLAGIARISM + ); + String message = messageTemplate.getMessageTemplate(); + String subject = messageTemplate.getSubjectTemplate(); + message = message.replaceAll("\\$\\$\\{ФИО_СТУД}", student.getParticipant().getFullName()); + subject = subject.replaceAll("\\$\\$\\{ФИО_СТУД}", student.getParticipant().getFullName()); + sendEmail(student.getParticipant().getEmail(), subject, message); + } + + public void sendDefensePreparationMessage(StudentData student) { + MessageTemplate messageTemplate = messageTemplateRepository.findByMessageType(MessageTemplate.MessageType.DEFENSE_PREPARATION_FOR_STUDENT); + String message = messageTemplate.getMessageTemplate(); + String subject = messageTemplate.getSubjectTemplate(); + message = message.replaceAll("\\$\\$\\{ФИО_СТУД}", student.getParticipant().getFullName()); + subject = subject.replaceAll("\\$\\$\\{ФИО_СТУД}", student.getParticipant().getFullName()); + sendEmail(student.getParticipant().getEmail(), subject, message); + + messageTemplate = messageTemplateRepository.findByMessageType(MessageTemplate.MessageType.DEFENSE_PREPARATION_FOR_TEACHER); + message = messageTemplate.getMessageTemplate(); + subject = messageTemplate.getSubjectTemplate(); + message = message.replaceAll("\\$\\$\\{ФИО_НАУЧ_РУК}", student.getCurator().getParticipant().getFullName()); + subject = subject.replaceAll("\\$\\$\\{ФИО_НАУЧ_РУК}", student.getCurator().getParticipant().getFullName()); + sendEmail(student.getCurator().getParticipant().getEmail(), subject, message); + } + + public void sendDefenseMessage(StudentData student) { + MessageTemplate messageTemplate = messageTemplateRepository.findByMessageType( + MessageTemplate.MessageType.DEFENSE + ); + String message = messageTemplate.getMessageTemplate(); + String subject = messageTemplate.getSubjectTemplate(); + message = message.replaceAll("\\$\\$\\{ФИО_СТУД}", student.getParticipant().getFullName()); + subject = subject.replaceAll("\\$\\$\\{ФИО_СТУД}", student.getParticipant().getFullName()); + sendEmail(student.getParticipant().getEmail(), subject, message); + } +} diff --git a/server/src/main/java/ru/mskobaro/tdms/business/service/GroupService.java b/server/src/main/java/ru/mskobaro/tdms/business/service/GroupService.java index 9518b84..59f7ad5 100644 --- a/server/src/main/java/ru/mskobaro/tdms/business/service/GroupService.java +++ b/server/src/main/java/ru/mskobaro/tdms/business/service/GroupService.java @@ -13,8 +13,8 @@ import ru.mskobaro.tdms.business.exception.BusinessException; import ru.mskobaro.tdms.integration.database.GroupRepository; import ru.mskobaro.tdms.integration.database.PreparationDirectionRepository; import ru.mskobaro.tdms.integration.database.StudentDataRepository; -import ru.mskobaro.tdms.presentation.controller.payload.GroupDTO; -import ru.mskobaro.tdms.presentation.controller.payload.StudentDataDTO; +import ru.mskobaro.tdms.integration.controller.payload.GroupDTO; +import ru.mskobaro.tdms.integration.controller.payload.StudentDataDTO; import java.util.Collection; import java.util.List; diff --git a/server/src/main/java/ru/mskobaro/tdms/business/service/HomeInfoService.java b/server/src/main/java/ru/mskobaro/tdms/business/service/HomeInfoService.java new file mode 100644 index 0000000..f396039 --- /dev/null +++ b/server/src/main/java/ru/mskobaro/tdms/business/service/HomeInfoService.java @@ -0,0 +1,59 @@ +package ru.mskobaro.tdms.business.service; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import org.springframework.stereotype.Service; +import ru.mskobaro.tdms.business.entity.Defense; +import ru.mskobaro.tdms.business.entity.Group; +import ru.mskobaro.tdms.integration.controller.HomeInfoController; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +public class HomeInfoService { + @PersistenceContext + private EntityManager entityManager; + + public HomeInfoController.DefaultInfo defaultInfo() { + HomeInfoController.DefaultInfo defaultInfo = new HomeInfoController.DefaultInfo(); + + Long userCount = entityManager + .createQuery("select count(*) from User u", Long.class) + .getSingleResult(); + Long particCount = entityManager + .createQuery("select count(*) from Participant u", Long.class) + .getSingleResult(); + defaultInfo.setParticCount(particCount); + defaultInfo.setUserCount(userCount); + + Long teacherCount = entityManager + .createQuery("select count(*) from TeacherData td", Long.class) + .getSingleResult(); + Long studentCount = entityManager + .createQuery("select count(*) from StudentData sd", Long.class) + .getSingleResult(); + defaultInfo.setTeacherCount(teacherCount); + defaultInfo.setStudentCount(studentCount); + + Long defenseCount = entityManager + .createQuery("select count(*) from Defense d", Long.class) + .getSingleResult(); + defaultInfo.setDefenseCount(defenseCount); + + Long currentDefenseCount = entityManager + .createQuery("select count(*) from Defense d where d.status not in :statuses", Long.class) + .setParameter("statuses", List.of(Defense.Status.FINISHED, Defense.Status.NOT_STARTED)) + .getSingleResult(); + defaultInfo.setCurrentDefenseCount(currentDefenseCount); + + List currentDefenseGroups = entityManager + .createQuery("select d.groups from Defense d where d.status not in :statuses", Group.class) + .setParameter("statuses", List.of(Defense.Status.FINISHED, Defense.Status.NOT_STARTED)) + .getResultList(); + String currentDefenseGroupNames = currentDefenseGroups.stream().map(Group::getName).collect(Collectors.joining(", ")); + defaultInfo.setCurrentDefenseGroupNames(currentDefenseGroupNames); + + return defaultInfo; + } +} diff --git a/server/src/main/java/ru/mskobaro/tdms/business/service/ParticipantService.java b/server/src/main/java/ru/mskobaro/tdms/business/service/ParticipantService.java index 0fe0974..d4387e7 100644 --- a/server/src/main/java/ru/mskobaro/tdms/business/service/ParticipantService.java +++ b/server/src/main/java/ru/mskobaro/tdms/business/service/ParticipantService.java @@ -1,9 +1,11 @@ package ru.mskobaro.tdms.business.service; -import io.micrometer.common.util.StringUtils; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.ListUtils; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @@ -11,8 +13,8 @@ import org.springframework.transaction.annotation.Transactional; import ru.mskobaro.tdms.business.entity.*; import ru.mskobaro.tdms.business.exception.AccessDeniedException; import ru.mskobaro.tdms.business.exception.BusinessException; +import ru.mskobaro.tdms.integration.controller.payload.ParticipantSaveDTO; import ru.mskobaro.tdms.integration.database.*; -import ru.mskobaro.tdms.presentation.controller.payload.ParticipantSaveDTO; import java.util.Collection; import java.util.Collections; @@ -41,6 +43,12 @@ public class ParticipantService { private AuthenticationService authenticationService; @Autowired private TeacherDataRepository teacherDataRepository; + @Autowired + private CommissionMemberRepository commissionMemberRepository; + @PersistenceContext + private EntityManager entityManager; + @Autowired + private EmailService emailService; public ParticipantService(ParticipantRepository participantRepository) { this.participantRepository = participantRepository; @@ -68,6 +76,15 @@ public class ParticipantService { participant.setFirstName(participantSaveDTO.getFirstName()); participant.setLastName(participantSaveDTO.getLastName()); participant.setMiddleName(participantSaveDTO.getMiddleName()); + + if (!editMode + || (!StringUtils.equals(participantSaveDTO.getEmail(), existingParticipant.getEmail()) + || !StringUtils.equals(participantSaveDTO.getNumberPhone(), existingParticipant.getNumberPhone()))) { + if (participantRepository.existsByNumberPhoneOrEmail(participantSaveDTO.getNumberPhone(), participantSaveDTO.getEmail())) { + throw new BusinessException("Номер телефона или Электронная почта уже используются"); + } + } + participant.setNumberPhone(participantSaveDTO.getNumberPhone()); participant.setEmail(participantSaveDTO.getEmail()); @@ -75,11 +92,24 @@ public class ParticipantService { boolean credentialsChanged = persistUserData(participantSaveDTO, existingParticipant, editMode, participant); persistStudentData(participantSaveDTO, existingParticipant, editMode, roles, participant); persistTeacherData(participantSaveDTO, existingParticipant, editMode, roles, participant); + persistCommissionData(participantSaveDTO, existingParticipant, editMode, roles, participant); + - // TODO: notification task Participant saved = participantRepository.save(participant); log.info("Participant saved: {}", saved.getFullName()); + if (!editMode && participantSaveDTO.getUserData() != null) { + emailService.sendEmail(saved.getEmail(), "Вам была создана учетная запись в системе поддержки ВКР", """ + Уважаемый, %s, + Вам была создана учетная запись в системе подготовки и защиты ВКР. + Ниже представлены данные для входа в систему: + Логин: %s + Пароль: %s + + С уважением, администрация. + """.formatted(participant.getFullName(), participant.getUser().getUsername(), participantSaveDTO.getUserData().getPassword())); + } + if (credentialsChanged) { log.info("User {} changed credentials, logging out", saved.getUser().getUsername()); authenticationService.logout(saved.getUser().getUsername()); @@ -205,10 +235,44 @@ public class ParticipantService { studentData.setCurator(null); } + studentData.setOtziv(participantSaveDTO.getStudentData().getOtziv()); + studentData.setWork(participantSaveDTO.getStudentData().getWork()); + studentData.setZachetka(participantSaveDTO.getStudentData().getZachetka()); + studentData.setPreDefenseMark(participantSaveDTO.getStudentData().getPreDefenseMark()); + studentData.setElectronic(participantSaveDTO.getStudentData().getElectronic()); + studentData.setOtlichiye(participantSaveDTO.getStudentData().getOtlichiye()); + studentData.setVnedreniye(participantSaveDTO.getStudentData().getVnedreniye()); + studentData.setMagistracy(participantSaveDTO.getStudentData().getMagistracy()); + + studentData.setMarks5(participantSaveDTO.getStudentData().getMarks5()); + studentData.setMarks4(participantSaveDTO.getStudentData().getMarks4()); + studentData.setMarks3(participantSaveDTO.getStudentData().getMarks3()); + studentData = studentDataRepository.save(studentData); studentData.setParticipant(participant); } + private void persistCommissionData(ParticipantSaveDTO participantSaveDTO, Participant existingParticipant, boolean editMode, List roles, Participant participant) { + boolean shouldPersist = participantSaveDTO.getCommMemData() != null && roles != null + && CollectionUtils.containsAny(roles, roleService.getRoleByAuthority(RoleService.Authority.COMM_MEMBER)); + if (!shouldPersist) { + return; + } + + boolean alreadyExists = editMode && commissionMemberRepository.existsByParticipant_Id(existingParticipant.getId()); + CommissionMemberData commMemData; + if (alreadyExists) { + commMemData = commissionMemberRepository.findByParticipant_Id(existingParticipant.getId()); + } else { + commMemData = new CommissionMemberData(); + } + + commMemData.setWorkPlace(participantSaveDTO.getCommMemData().getWorkPlace()); + commMemData.setWorkPosition(participantSaveDTO.getCommMemData().getWorkPosition()); + commMemData = commissionMemberRepository.save(commMemData); + commMemData.setParticipant(participant); + } + public void deleteParticipant(Long id) { if (id == 1) { throw new AccessDeniedException(); @@ -217,4 +281,10 @@ public class ParticipantService { Participant partic = participantRepository.findByIdThrow(id); partic.setDeleted(true); } + + public List getAllAntipl() { + return entityManager.createQuery("select p from Participant p inner join p.roles r where r.authority = :auth", Participant.class) + .setParameter("auth", RoleService.Authority.PLAGIARISM_CHECKER.getAuthority()) + .getResultList(); + } } diff --git a/server/src/main/java/ru/mskobaro/tdms/business/service/PreparationDirectionService.java b/server/src/main/java/ru/mskobaro/tdms/business/service/PreparationDirectionService.java index 16437b3..abe94b6 100644 --- a/server/src/main/java/ru/mskobaro/tdms/business/service/PreparationDirectionService.java +++ b/server/src/main/java/ru/mskobaro/tdms/business/service/PreparationDirectionService.java @@ -4,8 +4,10 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ru.mskobaro.tdms.business.entity.DirectionOfPreparation; +import ru.mskobaro.tdms.business.entity.TeacherData; import ru.mskobaro.tdms.integration.database.PreparationDirectionRepository; -import ru.mskobaro.tdms.presentation.controller.payload.PreparationDirectionDTO; +import ru.mskobaro.tdms.integration.controller.payload.PreparationDirectionDTO; +import ru.mskobaro.tdms.integration.database.TeacherDataRepository; import java.util.List; @@ -14,6 +16,8 @@ import java.util.List; public class PreparationDirectionService { @Autowired private PreparationDirectionRepository preparationDirectionRepository; + @Autowired + private TeacherDataRepository teacherDataRepository; public List getAll() { return preparationDirectionRepository.findAll(); @@ -27,8 +31,15 @@ public class PreparationDirectionService { } else { preparationDirection = new DirectionOfPreparation(); } + + TeacherData teacherData = null; + if (preparationDirectionDTO.getResponsible() != null && preparationDirectionDTO.getResponsible().getId() != null) { + teacherData = teacherDataRepository.findByIdThrow(preparationDirectionDTO.getResponsible().getId()); + } + preparationDirection.setName(preparationDirectionDTO.getName()); preparationDirection.setCode(preparationDirectionDTO.getCode()); + preparationDirection.setResponsible(teacherData); preparationDirectionRepository.save(preparationDirection); } } diff --git a/server/src/main/java/ru/mskobaro/tdms/business/service/StudentDataService.java b/server/src/main/java/ru/mskobaro/tdms/business/service/StudentDataService.java index a10ff24..acfa87b 100644 --- a/server/src/main/java/ru/mskobaro/tdms/business/service/StudentDataService.java +++ b/server/src/main/java/ru/mskobaro/tdms/business/service/StudentDataService.java @@ -1,11 +1,14 @@ package ru.mskobaro.tdms.business.service; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; import jakarta.transaction.Transactional; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import ru.mskobaro.tdms.business.entity.Participant; import ru.mskobaro.tdms.business.entity.StudentData; +import ru.mskobaro.tdms.integration.controller.payload.StudentDataDTO; import ru.mskobaro.tdms.integration.database.StudentDataRepository; import ru.mskobaro.tdms.integration.database.UserRepository; @@ -18,6 +21,8 @@ import java.util.List; public class StudentDataService { @Autowired private StudentDataRepository studentDataRepository; + @PersistenceContext + private EntityManager entityManager; public StudentData getStudentByParticIdThrow(Long particId) { return studentDataRepository.findStudentDataByParticipant_Id(particId); @@ -26,4 +31,9 @@ public class StudentDataService { public Collection getAllStudentsWithoutGroup() { return studentDataRepository.findByGroupIsNull(); } + + public List getByDefenseId(Long id) { + return entityManager.createQuery("select sd from StudentData sd where sd.group.defense.id = :id", StudentData.class) + .setParameter("id", id).getResultList(); + } } diff --git a/server/src/main/java/ru/mskobaro/tdms/business/service/TaskService.java b/server/src/main/java/ru/mskobaro/tdms/business/service/TaskService.java index 9075194..f7cfe50 100644 --- a/server/src/main/java/ru/mskobaro/tdms/business/service/TaskService.java +++ b/server/src/main/java/ru/mskobaro/tdms/business/service/TaskService.java @@ -1,17 +1,18 @@ package ru.mskobaro.tdms.business.service; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import ru.mskobaro.tdms.business.entity.StudentData; -import ru.mskobaro.tdms.business.entity.Task; -import ru.mskobaro.tdms.business.entity.User; -import ru.mskobaro.tdms.business.taskfields.DiplomaTopicAgreementTaskFields; -import ru.mskobaro.tdms.business.taskfields.TaskFields; -import ru.mskobaro.tdms.integration.database.StudentDataRepository; -import ru.mskobaro.tdms.integration.database.TaskRepository; -import ru.mskobaro.tdms.presentation.controller.TaskController; +import ru.mskobaro.tdms.business.entity.*; +import ru.mskobaro.tdms.business.exception.BusinessException; +import ru.mskobaro.tdms.integration.controller.TaskController; +import ru.mskobaro.tdms.integration.controller.payload.DefenseDTO; +import ru.mskobaro.tdms.integration.controller.payload.IdDTO; +import ru.mskobaro.tdms.integration.database.*; +import java.util.ArrayList; import java.util.List; @Service @@ -23,41 +24,496 @@ public class TaskService { private UserService userService; @Autowired private StudentDataRepository studentDataRepository; + @PersistenceContext + private EntityManager entityManager; + @Autowired + private ParticipantRepository participantRepository; + @Autowired + private DiplomaTopicRepository diplomaTopicRepository; + @Autowired + private TeacherDataRepository teacherDataRepository; + @Autowired + private RoleService roleService; + @Autowired + private EmailService emailService; + @Autowired + private DefenseRepository defenseRepository; - public void createTask(Task.Type type, Task.Status status, TaskFields taskFields) { - Task task = new Task(); - task.setType(type); - task.setStatus(status); - task.setFields(taskFields); - taskRepository.save(task); - } - - public Task findDiplomaTopicAgreementTaskCallerMaker() { + public TopicAgreementTask findDiplomaTopicAgreementTaskMadeByCurrentUser() { User user = userService.getCallerUser(); - List diplomaTopicAgreementTaskByMakerId = taskRepository.findDiplomaTopicAgreementTaskByMakerId( - user.getParticipant().getId(), Task.Type.DIPLOMA_TOPIC_AGREEMENT - ); - if (diplomaTopicAgreementTaskByMakerId.isEmpty()) { + try { + return entityManager.createQuery("select t from TopicAgreementTask t where t.checkerParticId = :id or t.makerParticId = :id", TopicAgreementTask.class) + .setParameter("id", user.getParticipant().getId()) + .setMaxResults(1) + .getSingleResult(); + } catch (Exception e) { return null; } + } - if (diplomaTopicAgreementTaskByMakerId.size() > 1) { - throw new IllegalStateException(); + private List getNotShowStatuses() { + User user = userService.getCallerUser(); + List notShowStatuses = new ArrayList<>(List.of(Task.Status.DONE, Task.Status.CANCELED)); + + if (!roleService.isParticInAuthority(user.getParticipant(), RoleService.Authority.STUDENT)) { + notShowStatuses.add(Task.Status.WAIT_FOR_STUDENT_EDIT); } - return diplomaTopicAgreementTaskByMakerId.get(0); + if (!roleService.isParticInAuthority(user.getParticipant(), RoleService.Authority.TEACHER)) { + notShowStatuses.add(Task.Status.WAIT_FOR_TOPIC_AGREEMENT); + } + return notShowStatuses; } - public void createDiplomaAgreementTask(TaskController.DiplomaTopicAgreementDTO diplomaTopicAgreementDTO) { - DiplomaTopicAgreementTaskFields taskFields = new DiplomaTopicAgreementTaskFields(); - User user = userService.getCallerUser(); - StudentData studentData = studentDataRepository.findStudentDataByParticipant_Id(user.getParticipant().getId()); + public Long getCurrentUserNotificationCount() { + return entityManager.createQuery( + """ + select count(t) from Task t where t.checkerParticId = :id + and t.status not in (:statuses) + """, Long.class) + .setParameter("id", userService.getCallerUser().getParticipant().getId()) + .setParameter("statuses", getNotShowStatuses()) + .getSingleResult(); + } - taskFields.setCheckerParticipantId(user.getParticipant().getId()); - taskFields.setDiplomaTopicId(diplomaTopicAgreementDTO.getDiplomaTopicId()); - taskFields.setDiplomaTopicName(diplomaTopicAgreementDTO.getDiplomaTopicName()); - taskFields.setCheckerParticipantId(studentData.getCurator().getId()); + public List getAllCurrentTasks() { + List tasks = entityManager.createQuery( + """ + select t from Task t + inner join fetch t.defense + where t.checkerParticId = :id + and t.status not in (:statuses) + """, Task.class) + .setParameter("id", userService.getCallerUser().getParticipant().getId()) + .setParameter("statuses", getNotShowStatuses()) + .getResultList(); + return tasks.stream().map(t -> { + TaskController.TaskDTO dto = new TaskController.TaskDTO(); + dto.setId(t.getId()); + dto.setStatus(t.getStatus()); + dto.setMessage(getTaskMessage(t)); + dto.setCheckerId(t.getCheckerParticId()); + dto.setMakerId(t.getMakerParticId()); + dto.setType(t.getType()); + dto.setDefense(DefenseDTO.from(t.getDefense())); - createTask(Task.Type.DIPLOMA_TOPIC_AGREEMENT, Task.Status.WAIT_FOR_TOPIC_AGREEMENT, taskFields); + if (t instanceof TopicAgreementTask tat) { + dto.setInfo(IdDTO.fromId(tat.getMakerParticId())); + } + + return dto; + }).toList(); + } + + private String getTaskMessage(Task t) { + switch (t.getStatus()) { + case WAIT_FOR_TOPIC_PREPARATION -> { + return "Требуется указать темы ВКР, которые смогут выбрать студенты"; + } + case WAIT_FOR_TOPIC_AGREEMENT -> { + Participant studentPartic = participantRepository.findByIdThrow(t.getMakerParticId()); + return "Согласование темы ВКР со студентом %s".formatted(studentPartic.getFullName()); + } + case WAIT_FOR_STUDENT_START -> { + return "Требуется согласовать тему с научным руководителем"; + } + case WAIT_FOR_STUDENT_EDIT -> { + return "Научный руководитель отправил тему на доработку"; + } + case WAIT_FOR_ANTIPL_PASSED -> { + Participant studPartic = participantRepository.findByIdThrow(t.getMakerParticId()); + return "После прохождения антиплагиата требуется пометить этап указать уникальность работы (%s)".formatted(studPartic.getFullName()); + } + case WAIT_FOR_NORMOCONTROL_PASSED -> { + Participant studPartic = participantRepository.findByIdThrow(t.getMakerParticId()); + return "После прохождения нормоконтроля требуется пометить этап как проеденный (%s)".formatted(studPartic.getFullName()); + } + case WAIT_FOR_TEACHER_OTZIV -> { + Participant studPartic = participantRepository.findByIdThrow(t.getMakerParticId()); + return "Требуется предоставить отзыв студенту (%s)".formatted(studPartic.getFullName()); + } + default -> { return t.getStatus().toString(); } + } + } + + public void agreementChangeRequest(TaskController.DiplomaTopicAgreementChangeRequestDTO changeRequest) { + TopicAgreementTask topicAgreementTask = entityManager.createQuery( + "select t from TopicAgreementTask t where t.id = :id", TopicAgreementTask.class) + .setParameter("id", changeRequest.getTaskId()) + .getSingleResult(); + + switch (changeRequest.getType()) { + case STUDENT_REDO -> { + if (changeRequest.getDiplomaTopicId() != null) { + topicAgreementTask.setDiplomaTopicId(changeRequest.getDiplomaTopicId()); + topicAgreementTask.setDiplomaTopicName(null); + } else if (changeRequest.getDiplomaTopicName() != null) { + topicAgreementTask.setDiplomaTopicName(changeRequest.getDiplomaTopicName()); + topicAgreementTask.setDiplomaTopicId(null); + } else { + throw new IllegalStateException(); + } + Long checkerId = topicAgreementTask.getCheckerParticId(); + StudentData student = studentDataRepository.findStudentDataByParticipant_Id(checkerId); + topicAgreementTask.setCheckerParticId(student.getCurator().getParticipant().getId()); + topicAgreementTask.setMakerParticId(checkerId); + topicAgreementTask.setStatus(Task.Status.WAIT_FOR_TOPIC_AGREEMENT); + } + case TEACHER_APPROVE -> { + StudentData student = studentDataRepository.findStudentDataByParticipant_Id(topicAgreementTask.getMakerParticId()); + if (topicAgreementTask.getDiplomaTopicId() != null) { + DiplomaTopic topic = diplomaTopicRepository.findByIdThrow(topicAgreementTask.getDiplomaTopicId()); + student.setDiplomaTopic(topic); + topicAgreementTask.setStatus(Task.Status.DONE); + } else if (topicAgreementTask.getDiplomaTopicName() != null) { + TeacherData teacherData = teacherDataRepository.findByParticipant_Id(topicAgreementTask.getCheckerParticId()); + DiplomaTopic diplomaTopic = new DiplomaTopic(); + diplomaTopic.setName(topicAgreementTask.getDiplomaTopicName()); + diplomaTopic.setTeacher(teacherData); + diplomaTopic.setDirectionOfPreparation(student.getGroup().getDirectionOfPreparation()); + topicAgreementTask.setStatus(Task.Status.DONE); + diplomaTopic = diplomaTopicRepository.save(diplomaTopic); + student.setDiplomaTopic(diplomaTopic); + } else { + throw new BusinessException("Должна быть указана тема"); + } + } + case TEACHER_REJECT -> { + Long checkerId = topicAgreementTask.getCheckerParticId(); + topicAgreementTask.setCheckerParticId(topicAgreementTask.getMakerParticId()); + topicAgreementTask.setMakerParticId(checkerId); + topicAgreementTask.setStatus(Task.Status.WAIT_FOR_STUDENT_EDIT); + } + } + } + + public TopicAgreementTask getTopicAgreementTaskByMakerId(Long id) { + try { + return entityManager.createQuery("select t from TopicAgreementTask t where t.makerParticId = :id", TopicAgreementTask.class) + .setParameter("id", id) + .setMaxResults(1) + .getSingleResult(); + } catch (Exception e) { + return null; + } + } + + public void checkAndCreateTopicPreparationTasksAndSendEmail(List teachers, Defense defense) { + for (TeacherData teacher : teachers) { + Boolean exists = entityManager.createQuery("select case when (count(t) > 0) then true else false end from Task t where t.checkerParticId = :id and t.type = :type and t.defense = :defense", Boolean.class) + .setParameter("id", teacher.getParticipant().getId()) + .setParameter("type", Task.Type.TOPIC_PREPARATION) + .setParameter("defense", defense) + .getSingleResult(); + + if (!exists) { + Task task = new Task(); + task.setDefense(defense); + task.setCheckerParticId(teacher.getParticipant().getId()); + task.setType(Task.Type.TOPIC_PREPARATION); + task.setStatus(Task.Status.WAIT_FOR_TOPIC_PREPARATION); + taskRepository.save(task); + emailService.sendTopicPreparationStartMessage(teacher); + } + } + } + + public void checkAndCreateTopicChoosingTasksAndSendEmail(List students, Defense defense) { + students.stream().filter(studentData -> studentData.getCurator() == null).forEach((sd) -> { + throw new BusinessException("Студенту %s не назначен научный руководитель".formatted(sd.getParticipant().getFullName())); + }); + + for (StudentData student : students) { + Boolean exists = entityManager.createQuery(""" + select case when (count(t) > 0) then true else false end from Task t + where (t.checkerParticId = :id or t.makerParticId = :id) + and t.type = :type + and t.defense = :defense + """, Boolean.class) + .setParameter("id", student.getParticipant().getId()) + .setParameter("type", Task.Type.TOPIC_AGREEMENT) + .setParameter("defense", defense) + .getSingleResult(); + + if (!exists) { + TopicAgreementTask topicAgreementTask = new TopicAgreementTask(); + topicAgreementTask.setCheckerParticId(student.getParticipant().getId()); + topicAgreementTask.setType(Task.Type.TOPIC_AGREEMENT); + topicAgreementTask.setStatus(Task.Status.WAIT_FOR_STUDENT_START); + topicAgreementTask.setDefense(defense); + taskRepository.save(topicAgreementTask); + emailService.sendTopicChoosingStartMessage(student.getCurator(), student); + } + } + } + + public void checkAndCreateTaskCreatingTask(List curators, Defense defense) { + for (TeacherData curator : curators) { + Boolean exists = entityManager.createQuery(""" + select case when (count(t) > 0) then true else false end from Task t + where t.checkerParticId = :id + and t.type = :type + and t.defense = :defense + """, Boolean.class) + .setParameter("id", curator.getParticipant().getId()) + .setParameter("type", Task.Type.TASK_CREATING) + .setParameter("defense", defense) + .getSingleResult(); + + if (!exists) { + Task task = new Task(); + task.setDefense(defense); + task.setCheckerParticId(curator.getParticipant().getId()); + task.setType(Task.Type.TASK_CREATING); + task.setStatus(Task.Status.DONE); + taskRepository.save(task); + emailService.sendTaskCreatingMessage(curator); + } + } + } + + public void checkAndCreateTaskAssignTasks(List curators, Defense defense) { + for (TeacherData curator : curators) { + Boolean exists = entityManager.createQuery(""" + select case when (count(t) > 0) then true else false end from Task t + where t.checkerParticId = :id + and t.type = :type + and t.defense = :defense + """, Boolean.class) + .setParameter("id", curator.getParticipant().getId()) + .setParameter("type", Task.Type.TASK_ASSIGN) + .setParameter("defense", defense) + .getSingleResult(); + + if (!exists) { + Task task = new Task(); + task.setDefense(defense); + task.setCheckerParticId(curator.getParticipant().getId()); + task.setType(Task.Type.TASK_ASSIGN); + task.setStatus(Task.Status.DONE); + taskRepository.save(task); + emailService.sendTaskAssigningMessage(curator); + } + } + } + + public void checkAndCreatePreDiplomaPracticeTask(List students, Defense defense) { + for (StudentData student : students) { + Boolean exists = entityManager.createQuery(""" + select case when (count(t) > 0) then true else false end from Task t + where (t.checkerParticId = :id or t.makerParticId = :id) + and t.type = :type + and t.defense = :defense + """, Boolean.class) + .setParameter("id", student.getParticipant().getId()) + .setParameter("type", Task.Type.PRE_DIPLOMA_PRACTICE) + .setParameter("defense", defense) + .getSingleResult(); + + if (!exists) { + PreDiplomaPracticeTask task = new PreDiplomaPracticeTask(); + task.setDefense(defense); + task.setCheckerParticId(student.getParticipant().getId()); + task.setType(Task.Type.PRE_DIPLOMA_PRACTICE); + task.setStatus(Task.Status.WAIT_FOR_STUDENT_PRE_DIPLOMA_PRACTICE_WORK_DONE); + taskRepository.save(task); + emailService.sendPreDiplomaPracticeMessage(student.getCurator(), student); + } + } + } + + public void checkAndCreateTopicFinalizationTask(Defense defense, List curators) { + for (TeacherData curator : curators) { + Boolean exists = entityManager.createQuery(""" + select case when (count(t) > 0) then true else false end from Task t + where t.checkerParticId = :id + and t.type = :type + and t.defense = :defense + """, Boolean.class) + .setParameter("id", curator.getParticipant().getId()) + .setParameter("type", Task.Type.TOPIC_FINALIZATION) + .setParameter("defense", defense) + .getSingleResult(); + + if (!exists) { + Task task = new Task(); + task.setDefense(defense); + task.setCheckerParticId(curator.getParticipant().getId()); + task.setType(Task.Type.TOPIC_FINALIZATION); + task.setStatus(Task.Status.DONE); + taskRepository.save(task); + emailService.sendTopicFinalizationMessage(curator); + } + } + } + + public void checkAndCreateMainVkrWorkTask(Defense defense, List students) { + for (StudentData student : students) { + Boolean exists = entityManager.createQuery(""" + select case when (count(t) > 0) then true else false end from Task t + where t.checkerParticId = :id + and t.type = :type + and t.defense = :defense + """, Boolean.class) + .setParameter("id", student.getParticipant().getId()) + .setParameter("type", Task.Type.TOPIC_FINALIZATION) + .setParameter("defense", defense) + .getSingleResult(); + if (!exists) { + Task task = new Task(); + task.setDefense(defense); + task.setCheckerParticId(student.getParticipant().getId()); + task.setType(Task.Type.MAIN_VKR_WORK); + task.setStatus(Task.Status.DONE); + taskRepository.save(task); + emailService.sendMainVrkWorkMessage(student); + } + } + } + + public void checkAndCreatePreDefenseTask(Defense defense, List students) { + for (StudentData student : students) { + Boolean exists = entityManager.createQuery(""" + select case when (count(t) > 0) then true else false end from Task t + where t.checkerParticId = :id + and t.type = :type + and t.defense = :defense + """, Boolean.class) + .setParameter("id", student.getParticipant().getId()) + .setParameter("type", Task.Type.PRE_DEFENSE) + .setParameter("defense", defense) + .getSingleResult(); + + if (!exists) { + Task task = new Task(); + task.setDefense(defense); + task.setCheckerParticId(student.getParticipant().getId()); + task.setType(Task.Type.PRE_DEFENSE); + task.setStatus(Task.Status.DONE); + taskRepository.save(task); + emailService.sendPreDefenseStartMessage(student); + } + } + } + + public void checkAndCreateNormcontrolAndAntiplagiarismTask(Defense defense, List students) { + for (StudentData student : students) { + Boolean exists = entityManager.createQuery(""" + select case when (count(t) > 0) then true else false end from Task t + where t.makerParticId = :id + and t.type = :type + and t.defense = :defense + """, Boolean.class) + .setParameter("id", student.getParticipant().getId()) + .setParameter("type", Task.Type.NORMCONTROL_AND_ANTIPLAGIARISM) + .setParameter("defense", defense) + .getSingleResult(); + + if (!exists) { + NormcontrolAndAntiplagiarismTask task = new NormcontrolAndAntiplagiarismTask(); + task.setDefense(defense); + task.setType(Task.Type.NORMCONTROL_AND_ANTIPLAGIARISM); + task.setStatus(Task.Status.WAIT_FOR_NORMOCONTROL_PASSED); + task.setCheckerParticId(defense.getResponsibleForNormcontrol().getId()); + task.setMakerParticId(student.getParticipant().getId()); + taskRepository.save(task); + emailService.sendNormcontrolAndAntiplagiarismMessage(student); + } + } + } + + public void checkAndCreateDefensePreparationTasks(Defense defense, List students) { + for (StudentData student : students) { + Boolean exists = entityManager.createQuery(""" + select case when (count(t) > 0) then true else false end from Task t + where t.makerParticId = :id + and t.type = :type + and t.defense = :defense + """, Boolean.class) + .setParameter("id", student.getParticipant().getId()) + .setParameter("type", Task.Type.DEFENSE_PREPARATION) + .setParameter("defense", defense) + .getSingleResult(); + + if (!exists) { + Task task = new Task(); + task.setDefense(defense); + task.setType(Task.Type.DEFENSE_PREPARATION); + task.setStatus(Task.Status.WAIT_FOR_TEACHER_OTZIV); + task.setMakerParticId(student.getParticipant().getId()); + task.setCheckerParticId(student.getCurator().getParticipant().getId()); + taskRepository.save(task); + emailService.sendDefensePreparationMessage(student); + } + } + } + + public void checkAndCreateDefenseTask(Defense defense, List students) { + for (StudentData student : students) { + Boolean exists = entityManager.createQuery(""" + select case when (count(t) > 0) then true else false end from Task t + where t.checkerParticId = :id + and t.type = :type + and t.defense = :defense + """, Boolean.class) + .setParameter("id", student.getParticipant().getId()) + .setParameter("type", Task.Type.DEFENSE) + .setParameter("defense", defense) + .getSingleResult(); + + if (!exists) { + Task task = new Task(); + task.setDefense(defense); + task.setType(Task.Type.DEFENSE); + task.setStatus(Task.Status.DONE); + task.setCheckerParticId(student.getParticipant().getId()); + taskRepository.save(task); + emailService.sendDefenseMessage(student); + } + } + } + + public void clearAllTasksWithDefense(Defense defense) { + entityManager.createQuery("delete from Task t where t.defense = :defense") + .setParameter("defense", defense) + .executeUpdate(); + } + + public void finishTopicPreparationTask(IdDTO defenseId) { + Defense defense = defenseRepository.findByIdThrow(defenseId.getId()); + Task task = entityManager.createQuery(""" + select t from Task t + where t.checkerParticId = :id + and t.type = :type + and t.defense = :defense + """, Task.class) + .setParameter("id", userService.getCallerUser().getParticipant().getId()) + .setParameter("type", Task.Type.TOPIC_PREPARATION) + .setParameter("defense", defense) + .getSingleResult(); + task.setStatus(Task.Status.DONE); + } + + public void finishDefPrepTask(TaskController.DefPrepFinishDTO dto) { + Task task = entityManager.createQuery("select t from Task t where t.id = :id", Task.class) + .setParameter("id", dto.getTaskId()) + .getSingleResult(); + Long studentParticId = task.getMakerParticId(); + StudentData student = studentDataRepository.findStudentDataByParticipant_Id(studentParticId); + student.setOtziv(dto.getMark()); + } + + public void finishNormAntipl(TaskController.NormAntiplFinishDTO dto) { + Task task = entityManager.createQuery("select t from Task t where t.id = :id", Task.class) + .setParameter("id", dto.getTaskId()) + .getSingleResult(); + StudentData student = studentDataRepository.findStudentDataByParticipant_Id(task.getMakerParticId()); + if (task.getStatus() == Task.Status.WAIT_FOR_NORMOCONTROL_PASSED) { + student.setNormcontrol(true); + task.setStatus(Task.Status.WAIT_FOR_ANTIPL_PASSED); + } else if (task.getStatus() == Task.Status.WAIT_FOR_ANTIPL_PASSED) { + student.setAntiplagiarism(dto.getAntipl()); + task.setStatus(Task.Status.DONE); + } } } diff --git a/server/src/main/java/ru/mskobaro/tdms/business/service/UserService.java b/server/src/main/java/ru/mskobaro/tdms/business/service/UserService.java index 51fbd6d..1759a69 100644 --- a/server/src/main/java/ru/mskobaro/tdms/business/service/UserService.java +++ b/server/src/main/java/ru/mskobaro/tdms/business/service/UserService.java @@ -9,7 +9,7 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import ru.mskobaro.tdms.business.entity.User; import ru.mskobaro.tdms.integration.database.UserRepository; -import ru.mskobaro.tdms.presentation.controller.payload.UserDTO; +import ru.mskobaro.tdms.integration.controller.payload.UserDTO; import java.util.List; diff --git a/server/src/main/java/ru/mskobaro/tdms/business/taskfields/DiplomaTopicAgreementTaskFields.java b/server/src/main/java/ru/mskobaro/tdms/business/taskfields/DiplomaTopicAgreementTaskFields.java deleted file mode 100644 index 07fa3c3..0000000 --- a/server/src/main/java/ru/mskobaro/tdms/business/taskfields/DiplomaTopicAgreementTaskFields.java +++ /dev/null @@ -1,11 +0,0 @@ -package ru.mskobaro.tdms.business.taskfields; - -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -public class DiplomaTopicAgreementTaskFields extends MakerCheckerTaskFields { - private String diplomaTopicName; - private Long diplomaTopicId; -} diff --git a/server/src/main/java/ru/mskobaro/tdms/business/taskfields/MakerCheckerTaskFields.java b/server/src/main/java/ru/mskobaro/tdms/business/taskfields/MakerCheckerTaskFields.java deleted file mode 100644 index dce60eb..0000000 --- a/server/src/main/java/ru/mskobaro/tdms/business/taskfields/MakerCheckerTaskFields.java +++ /dev/null @@ -1,13 +0,0 @@ -package ru.mskobaro.tdms.business.taskfields; - -import lombok.Getter; -import lombok.Setter; - -import java.time.LocalDateTime; - -@Getter -@Setter -public class MakerCheckerTaskFields extends MakerTaskFields { - private Long checkerParticipantId; - private LocalDateTime approvedAt; -} diff --git a/server/src/main/java/ru/mskobaro/tdms/business/taskfields/MakerTaskFields.java b/server/src/main/java/ru/mskobaro/tdms/business/taskfields/MakerTaskFields.java deleted file mode 100644 index c27b1ac..0000000 --- a/server/src/main/java/ru/mskobaro/tdms/business/taskfields/MakerTaskFields.java +++ /dev/null @@ -1,10 +0,0 @@ -package ru.mskobaro.tdms.business.taskfields; - -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -public class MakerTaskFields extends TaskFields { - private Long makerParticipantId; -} diff --git a/server/src/main/java/ru/mskobaro/tdms/business/taskfields/TaskFields.java b/server/src/main/java/ru/mskobaro/tdms/business/taskfields/TaskFields.java deleted file mode 100644 index 20b8a27..0000000 --- a/server/src/main/java/ru/mskobaro/tdms/business/taskfields/TaskFields.java +++ /dev/null @@ -1,10 +0,0 @@ -package ru.mskobaro.tdms.business.taskfields; - -import lombok.Getter; - -import java.time.LocalDateTime; - -@Getter -public class TaskFields { - private LocalDateTime createdAt; -} diff --git a/server/src/main/java/ru/mskobaro/tdms/integration/controller/CommissionMemberController.java b/server/src/main/java/ru/mskobaro/tdms/integration/controller/CommissionMemberController.java new file mode 100644 index 0000000..ae75a03 --- /dev/null +++ b/server/src/main/java/ru/mskobaro/tdms/integration/controller/CommissionMemberController.java @@ -0,0 +1,33 @@ +package ru.mskobaro.tdms.integration.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import ru.mskobaro.tdms.business.entity.CommissionMemberData; +import ru.mskobaro.tdms.integration.controller.payload.CommissionMemberDTO; +import ru.mskobaro.tdms.integration.database.CommissionMemberRepository; + +import java.util.List; +import java.util.stream.Collectors; + +@RestController +@RequestMapping("api/v1/commission-member") +public class CommissionMemberController { + @Autowired + private CommissionMemberRepository commissionMemberRepository; + + @GetMapping("/get-all") + public List getAll() { + List all = commissionMemberRepository.findAll(); + return all.stream().map(CommissionMemberDTO::from).collect(Collectors.toList()); + } + + @GetMapping("/by-partic-id") + public CommissionMemberDTO getByParticId(@RequestParam("id") Long id) { + CommissionMemberData commissionMemberData = commissionMemberRepository.findByParticipant_Id(id); + return CommissionMemberDTO.from(commissionMemberData); + } + +} diff --git a/server/src/main/java/ru/mskobaro/tdms/integration/controller/DefenseController.java b/server/src/main/java/ru/mskobaro/tdms/integration/controller/DefenseController.java new file mode 100644 index 0000000..81a39fe --- /dev/null +++ b/server/src/main/java/ru/mskobaro/tdms/integration/controller/DefenseController.java @@ -0,0 +1,121 @@ +package ru.mskobaro.tdms.integration.controller; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import ru.mskobaro.tdms.business.service.DefenseService; +import ru.mskobaro.tdms.integration.controller.payload.DefenseDTO; +import ru.mskobaro.tdms.integration.controller.payload.IdDTO; + +import java.util.List; +import java.util.stream.Collectors; + +@RestController +@RequestMapping("/api/v1/defense") +public class DefenseController { + @Autowired + private DefenseService defenseService; + + @GetMapping("get-all") + public List getAllDefenses() { + return defenseService.findAll().stream().map(DefenseDTO::from).collect(Collectors.toList()); + } + + @GetMapping("get-by-id") + public DefenseDTO getById(@RequestParam Long id) { + return DefenseDTO.from(defenseService.findById(id)); + } + + @PostMapping("save") + public void save(@RequestBody DefenseDTO defenseDTO) { + defenseService.save(defenseDTO); + } + + @PostMapping("to-next-state") + public void nextState(@RequestBody IdDTO idDto) { + defenseService.handleNextStateRequest(idDto.getId()); + } + + @GetMapping("defense-table") + public DefenseTableData getDefenseTable(@RequestParam Long defenseId) { + return defenseService.tableDataByDefenseId(defenseId); + } + + @PostMapping("defense-table-save") + public void defenseTableSave(@RequestBody DefenseTableData defenseTableData) { + defenseService.tableDataSave(defenseTableData); + } + + @Getter + @Setter + public static class DefenseTableData { + private Long defenseId; + private List groups; + } + + @Getter + @Setter + public static class DefenseTableDataGroup { + private Long groupId; + private Long studId; + private String groupName; + + private String fio; + + private Integer pdpMark; + private String topic; + + private String teacherFio; + + private Integer dayOfProt; + private Integer placeOfProt; + + private Boolean magistracy; + private Boolean electronic; + + private Integer otziv; + private Integer dpedefenseMark; + + private Boolean normcontrol; + private Integer antipl; + + private Boolean zachetka; + private String work; + + private Boolean vnedreniye; + private Boolean otlichiye; + } + + @PostMapping("recreate-protection-order") + public void recreateProtectionOrder(@RequestBody IdDTO idDto) { + defenseService.recreateProtectionOrder(idDto.getId()); + } + + @PostMapping("save-order") + public void updateProtectionOrder(@RequestBody ProtOrDTO dto) { + defenseService.updateProtOrder(dto); + } + + @Getter + @Setter + public static class ProtOrDTO { + private Long defenseId; + private List days; + private List unassigned; + } + + @Getter + @Setter + public static class DrotOrGroupDTO { + private Integer number; + private List studs; + } + + @Getter + @Setter + public static class StudDTO { + private Long id; + private Integer order; + } +} diff --git a/server/src/main/java/ru/mskobaro/tdms/integration/controller/DiplomaTopicController.java b/server/src/main/java/ru/mskobaro/tdms/integration/controller/DiplomaTopicController.java new file mode 100644 index 0000000..370bbea --- /dev/null +++ b/server/src/main/java/ru/mskobaro/tdms/integration/controller/DiplomaTopicController.java @@ -0,0 +1,49 @@ +package ru.mskobaro.tdms.integration.controller; + + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import ru.mskobaro.tdms.business.entity.DiplomaTopic; +import ru.mskobaro.tdms.business.service.DiplomaTopicService; +import ru.mskobaro.tdms.integration.controller.payload.DiplomaTopicDTO; +import ru.mskobaro.tdms.integration.database.DiplomaTopicRepository; + +import java.util.List; + + +@RestController +@RequestMapping("/api/v1/diploma-topic/") +@Validated +public class DiplomaTopicController { + @Autowired + private DiplomaTopicService diplomaTopicService; + @Autowired + private DiplomaTopicRepository diplomaTopicRepository; + + @GetMapping("/all") + public List getAll() { + return diplomaTopicService.findAll().stream().map(DiplomaTopicDTO::from).toList(); + } + + @PostMapping("/save") + public void save(@RequestBody DiplomaTopicDTO diplomaTopicDTO) { + diplomaTopicService.save(diplomaTopicDTO); + } + + @GetMapping("/all-for-student") + public List getAllForStudent(@RequestParam Long id) { + return diplomaTopicService.findAllForStudentByParticId(id).stream().map(DiplomaTopicDTO::from).toList(); + } + + @GetMapping("/by-id") + public DiplomaTopicDTO getById(@RequestParam Long id) { + DiplomaTopic topic = diplomaTopicRepository.findByIdThrow(id); + return DiplomaTopicDTO.from(topic); + } + + @GetMapping("matching-teacher-and-dir-prep") + public List matchingTeacherAndDirPrep(@RequestParam Long particId, @RequestParam Long dirPrepId) { + return diplomaTopicService.findByTeacherParticIdAndDirPrep(particId, dirPrepId).stream().map(DiplomaTopicDTO::from).toList(); + } +} diff --git a/server/src/main/java/ru/mskobaro/tdms/integration/controller/DocumentsController.java b/server/src/main/java/ru/mskobaro/tdms/integration/controller/DocumentsController.java new file mode 100644 index 0000000..adb712d --- /dev/null +++ b/server/src/main/java/ru/mskobaro/tdms/integration/controller/DocumentsController.java @@ -0,0 +1,46 @@ +package ru.mskobaro.tdms.integration.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import ru.mskobaro.tdms.business.service.DocumentsService; + +@RestController +@RequestMapping("api/v1/doc") +public class DocumentsController { + @Autowired + private DocumentsService documentsService; + + @GetMapping("stud-list") + public ResponseEntity getStudList(@RequestParam Long id) { + byte[] document = documentsService.getStudList(id, "бакалавр"); + return returnFile(document, "SPISOK_ZASHISHAYUSHIHSYA.docx"); + } + + @GetMapping("gek-questions") + public ResponseEntity getQuestions(@RequestParam Long id, @RequestParam String studyForm) { + byte[] document = documentsService.getQuestionsDocument(id, studyForm); + return returnFile(document, "BLANK_VOPROSOV_GEK.docx"); + } + + @GetMapping("topic-list") + public ResponseEntity getTopicList(@RequestParam Long id) { + byte[] document = documentsService.getTopicList(id); + return returnFile(document, "FRAGMENT_PRIKAZA_TEMI_VKR.docx"); + } + + private ResponseEntity returnFile(byte[] document, String filename) { + ByteArrayResource resource = new ByteArrayResource(document); + return ResponseEntity.ok() + .contentType(MediaType.APPLICATION_OCTET_STREAM) + .contentLength(document.length) + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + filename) + .body(resource); + } +} diff --git a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/GroupController.java b/server/src/main/java/ru/mskobaro/tdms/integration/controller/GroupController.java similarity index 87% rename from server/src/main/java/ru/mskobaro/tdms/presentation/controller/GroupController.java rename to server/src/main/java/ru/mskobaro/tdms/integration/controller/GroupController.java index 2eeb7e0..df04739 100644 --- a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/GroupController.java +++ b/server/src/main/java/ru/mskobaro/tdms/integration/controller/GroupController.java @@ -1,10 +1,10 @@ -package ru.mskobaro.tdms.presentation.controller; +package ru.mskobaro.tdms.integration.controller; import jakarta.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import ru.mskobaro.tdms.business.service.GroupService; -import ru.mskobaro.tdms.presentation.controller.payload.GroupDTO; +import ru.mskobaro.tdms.integration.controller.payload.GroupDTO; import java.util.Collection; diff --git a/server/src/main/java/ru/mskobaro/tdms/integration/controller/HomeInfoController.java b/server/src/main/java/ru/mskobaro/tdms/integration/controller/HomeInfoController.java new file mode 100644 index 0000000..3a8ddf4 --- /dev/null +++ b/server/src/main/java/ru/mskobaro/tdms/integration/controller/HomeInfoController.java @@ -0,0 +1,33 @@ +package ru.mskobaro.tdms.integration.controller; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import ru.mskobaro.tdms.business.service.HomeInfoService; + +@RestController +@RequestMapping("/api/v1/home-page/") +public class HomeInfoController { + @Autowired + private HomeInfoService homeInfoService; + + @GetMapping("default-info") + public DefaultInfo defaultInfo() { + return homeInfoService.defaultInfo(); + } + + @Getter + @Setter + public static class DefaultInfo { + private Long particCount; + private Long userCount; + private Long teacherCount; + private Long studentCount; + private Long defenseCount; + private Long currentDefenseCount; + private String currentDefenseGroupNames; + } +} diff --git a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/ParticipantController.java b/server/src/main/java/ru/mskobaro/tdms/integration/controller/ParticipantController.java similarity index 53% rename from server/src/main/java/ru/mskobaro/tdms/presentation/controller/ParticipantController.java rename to server/src/main/java/ru/mskobaro/tdms/integration/controller/ParticipantController.java index 7a949c1..43dc406 100644 --- a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/ParticipantController.java +++ b/server/src/main/java/ru/mskobaro/tdms/integration/controller/ParticipantController.java @@ -1,14 +1,16 @@ -package ru.mskobaro.tdms.presentation.controller; +package ru.mskobaro.tdms.integration.controller; import jakarta.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import ru.mskobaro.tdms.business.service.ParticipantService; -import ru.mskobaro.tdms.presentation.controller.payload.IdDto; -import ru.mskobaro.tdms.presentation.controller.payload.ParticipantDTO; -import ru.mskobaro.tdms.presentation.controller.payload.ParticipantSaveDTO; +import ru.mskobaro.tdms.integration.controller.payload.IdDTO; +import ru.mskobaro.tdms.integration.controller.payload.ParticipantDTO; +import ru.mskobaro.tdms.integration.controller.payload.ParticipantSaveDTO; import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; @RestController @RequestMapping("/api/v1/participant") @@ -17,7 +19,7 @@ public class ParticipantController { private ParticipantService participantService; @GetMapping("/get-all-participants") - public Collection getAllParticipants() { + public List getAllParticipants() { return participantService.getAllParticipants().stream() .map(ParticipantDTO::fromEntity) .toList(); @@ -29,7 +31,17 @@ public class ParticipantController { } @PostMapping("/delete") - public void deleteParticipant(@RequestBody IdDto id) { + public void deleteParticipant(@RequestBody IdDTO id) { participantService.deleteParticipant(id.getId()); } + + @GetMapping("get-all-antipl") + public List getAllAntipl() { + return participantService.getAllAntipl().stream().map(ParticipantDTO::fromEntity).collect(Collectors.toList()); + } + + @GetMapping("get-all-norm") + public List getAllNorm() { + return this.getAllParticipants(); + } } diff --git a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/PreparationDirectionController.java b/server/src/main/java/ru/mskobaro/tdms/integration/controller/PreparationDirectionController.java similarity index 86% rename from server/src/main/java/ru/mskobaro/tdms/presentation/controller/PreparationDirectionController.java rename to server/src/main/java/ru/mskobaro/tdms/integration/controller/PreparationDirectionController.java index e2c06be..192024c 100644 --- a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/PreparationDirectionController.java +++ b/server/src/main/java/ru/mskobaro/tdms/integration/controller/PreparationDirectionController.java @@ -1,9 +1,9 @@ -package ru.mskobaro.tdms.presentation.controller; +package ru.mskobaro.tdms.integration.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import ru.mskobaro.tdms.business.service.PreparationDirectionService; -import ru.mskobaro.tdms.presentation.controller.payload.PreparationDirectionDTO; +import ru.mskobaro.tdms.integration.controller.payload.PreparationDirectionDTO; import java.util.List; import java.util.stream.Collectors; diff --git a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/StudentController.java b/server/src/main/java/ru/mskobaro/tdms/integration/controller/StudentController.java similarity index 60% rename from server/src/main/java/ru/mskobaro/tdms/presentation/controller/StudentController.java rename to server/src/main/java/ru/mskobaro/tdms/integration/controller/StudentController.java index cd436f2..f1cbd77 100644 --- a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/StudentController.java +++ b/server/src/main/java/ru/mskobaro/tdms/integration/controller/StudentController.java @@ -1,4 +1,4 @@ -package ru.mskobaro.tdms.presentation.controller; +package ru.mskobaro.tdms.integration.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; @@ -7,16 +7,19 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import ru.mskobaro.tdms.business.entity.StudentData; import ru.mskobaro.tdms.business.service.StudentDataService; -import ru.mskobaro.tdms.presentation.controller.payload.GroupDTO; -import ru.mskobaro.tdms.presentation.controller.payload.StudentDataDTO; +import ru.mskobaro.tdms.integration.controller.payload.StudentDataDTO; +import ru.mskobaro.tdms.integration.database.StudentDataRepository; import java.util.Collection; +import java.util.List; @RestController @RequestMapping("/api/v1/student/") public class StudentController { @Autowired private StudentDataService studentDataService; + @Autowired + private StudentDataRepository studentDataRepository; @GetMapping(value = "/by-partic-id") public StudentDataDTO getCurrentStudent(@RequestParam(value = "id") Long particId) { @@ -30,4 +33,16 @@ public class StudentController { .map(sd -> StudentDataDTO.from(sd, true)) .toList(); } + + @GetMapping(value = "by-id") + public StudentDataDTO getStudentById(@RequestParam(value = "id") Long id) { + return StudentDataDTO.from(studentDataRepository.findByIdThrow(id), true); + } + + @GetMapping("by-defense-id") + public List getByDefenseId(@RequestParam(value = "id") Long id) { + return studentDataService.getByDefenseId(id).stream() + .map(studentData -> StudentDataDTO.from(studentData, true)) + .toList(); + } } diff --git a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/SysInfoController.java b/server/src/main/java/ru/mskobaro/tdms/integration/controller/SysInfoController.java similarity index 92% rename from server/src/main/java/ru/mskobaro/tdms/presentation/controller/SysInfoController.java rename to server/src/main/java/ru/mskobaro/tdms/integration/controller/SysInfoController.java index eca4bf3..c82e99e 100644 --- a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/SysInfoController.java +++ b/server/src/main/java/ru/mskobaro/tdms/integration/controller/SysInfoController.java @@ -1,4 +1,4 @@ -package ru.mskobaro.tdms.presentation.controller; +package ru.mskobaro.tdms.integration.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; diff --git a/server/src/main/java/ru/mskobaro/tdms/integration/controller/TaskController.java b/server/src/main/java/ru/mskobaro/tdms/integration/controller/TaskController.java new file mode 100644 index 0000000..76912d8 --- /dev/null +++ b/server/src/main/java/ru/mskobaro/tdms/integration/controller/TaskController.java @@ -0,0 +1,122 @@ +package ru.mskobaro.tdms.integration.controller; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import ru.mskobaro.tdms.business.entity.Task; +import ru.mskobaro.tdms.business.entity.TopicAgreementTask; +import ru.mskobaro.tdms.business.service.TaskService; +import ru.mskobaro.tdms.integration.controller.payload.DefenseDTO; +import ru.mskobaro.tdms.integration.controller.payload.IdDTO; +import ru.mskobaro.tdms.integration.controller.payload.TopicAgreementDto; + +import java.util.List; + +@RestController +@RequestMapping("/api/v1/task") +public class TaskController { + @Autowired + private TaskService taskService; + + @GetMapping("/diploma-agreement-maker") + public TopicAgreementDto diplomaTopicAgreementMaker() { + TopicAgreementTask task = taskService.findDiplomaTopicAgreementTaskMadeByCurrentUser(); + return task == null ? null : TopicAgreementDto.from(task); + } + + @GetMapping("/diploma-agreement-maker-by-partic-id") + public TopicAgreementDto diplomaTopicAgreementMakerId(@RequestParam Long id) { + TopicAgreementTask task = taskService.getTopicAgreementTaskByMakerId(id); + return task == null ? null : TopicAgreementDto.from(task); + } + + @PostMapping("/agreement-change") + public void agreementChange(@RequestBody DiplomaTopicAgreementChangeRequestDTO changeRequest) { + taskService.agreementChangeRequest(changeRequest); + } + + @GetMapping("/notif-count") + public NumberDTO getCurrentUserNotificationCount() { + NumberDTO dto = new NumberDTO(); + Long number = taskService.getCurrentUserNotificationCount(); + dto.setNumber(number); + return dto; + } + + @GetMapping("/get-current-tasks") + public List getCurrentTasks() { + return taskService.getAllCurrentTasks(); + } + + @Getter + @Setter + public static class DiplomaTopicAgreementDTO { + private String diplomaTopicName; + private Long diplomaTopicId; + } + + @Getter + @Setter + public static class NumberDTO { + private Long number; + } + + @Getter + @Setter + public static class TaskDTO { + private Long id; + private String message; + private Task.Status status; + private Task.Type type; + private Long makerId; + private Long checkerId; + private DefenseDTO defense; + private Object info; + } + + @Getter + @Setter + public static class DiplomaTopicAgreementChangeRequestDTO { + public enum ChangeType { + STUDENT_REDO, + TEACHER_APPROVE, + TEACHER_REJECT, + } + + private Long taskId; + private String diplomaTopicName; + private Long diplomaTopicId; + private ChangeType type; + } + + @PostMapping("topic-preparation-finish") + public void finishTopicPreparation(@RequestBody IdDTO idDTO) { + taskService.finishTopicPreparationTask(idDTO); + } + + @PostMapping("def-prep-finish") + public void finishDefPreparation(@RequestBody DefPrepFinishDTO defPrepFinishDTO) { + taskService.finishDefPrepTask(defPrepFinishDTO); + } + + @Getter + @Setter + public static class DefPrepFinishDTO { + private Long taskId; + private Integer mark; + } + + @PostMapping("norm-antipl-finish") + public void finishNormAntiplFinish(@RequestBody NormAntiplFinishDTO antiplFinishDTO) { + taskService.finishNormAntipl(antiplFinishDTO); + } + + @Getter + @Setter + public static class NormAntiplFinishDTO { + private Long taskId; + private Integer antipl; + } +} diff --git a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/TeacherDataController.java b/server/src/main/java/ru/mskobaro/tdms/integration/controller/TeacherDataController.java similarity index 88% rename from server/src/main/java/ru/mskobaro/tdms/presentation/controller/TeacherDataController.java rename to server/src/main/java/ru/mskobaro/tdms/integration/controller/TeacherDataController.java index 5e712e3..188709e 100644 --- a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/TeacherDataController.java +++ b/server/src/main/java/ru/mskobaro/tdms/integration/controller/TeacherDataController.java @@ -1,4 +1,4 @@ -package ru.mskobaro.tdms.presentation.controller; +package ru.mskobaro.tdms.integration.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; @@ -6,7 +6,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import ru.mskobaro.tdms.business.service.TeacherDataService; -import ru.mskobaro.tdms.presentation.controller.payload.TeacherDataDTO; +import ru.mskobaro.tdms.integration.controller.payload.TeacherDataDTO; import java.util.List; diff --git a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/UserController.java b/server/src/main/java/ru/mskobaro/tdms/integration/controller/UserController.java similarity index 87% rename from server/src/main/java/ru/mskobaro/tdms/presentation/controller/UserController.java rename to server/src/main/java/ru/mskobaro/tdms/integration/controller/UserController.java index b02b0be..cf20c5e 100644 --- a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/UserController.java +++ b/server/src/main/java/ru/mskobaro/tdms/integration/controller/UserController.java @@ -1,4 +1,4 @@ -package ru.mskobaro.tdms.presentation.controller; +package ru.mskobaro.tdms.integration.controller; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; @@ -9,8 +9,8 @@ import org.springframework.web.bind.annotation.*; import ru.mskobaro.tdms.business.entity.User; import ru.mskobaro.tdms.business.service.AuthenticationService; import ru.mskobaro.tdms.business.service.UserService; -import ru.mskobaro.tdms.presentation.controller.payload.LoginDTO; -import ru.mskobaro.tdms.presentation.controller.payload.UserDTO; +import ru.mskobaro.tdms.integration.controller.payload.LoginDTO; +import ru.mskobaro.tdms.integration.controller.payload.UserDTO; @RestController @RequestMapping("/api/v1/user") diff --git a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/CommissionMemberDTO.java b/server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/CommissionMemberDTO.java similarity index 94% rename from server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/CommissionMemberDTO.java rename to server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/CommissionMemberDTO.java index 2f5b95a..9524034 100644 --- a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/CommissionMemberDTO.java +++ b/server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/CommissionMemberDTO.java @@ -1,4 +1,4 @@ -package ru.mskobaro.tdms.presentation.controller.payload; +package ru.mskobaro.tdms.integration.controller.payload; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/DefenseDTO.java b/server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/DefenseDTO.java new file mode 100644 index 0000000..6a83d83 --- /dev/null +++ b/server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/DefenseDTO.java @@ -0,0 +1,58 @@ +package ru.mskobaro.tdms.integration.controller.payload; + +import lombok.Getter; +import lombok.Setter; +import org.apache.commons.collections4.CollectionUtils; +import ru.mskobaro.tdms.business.entity.Defense; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +@Getter +@Setter +public class DefenseDTO { + private Long id; + private List commissionMembers; + private List groups; + private Defense.Status status; + private ParticipantDTO responsibleForAntipl; + private ParticipantDTO responsibleForNorm; + private PreparationDirectionDTO preparationDirection; + private LocalDate defenseDate; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + + public static DefenseDTO from(Defense defense) { + DefenseDTO dto = new DefenseDTO(); + dto.setId(defense.getId()); + + if (CollectionUtils.isNotEmpty(defense.getCommissionMembers())) { + dto.setCommissionMembers( + defense.getCommissionMembers().stream() + .map(CommissionMemberDTO::from) + .collect(Collectors.toList()) + ); + } + + if (defense.getResponsibleForAntiplagiarism() != null) { + dto.setResponsibleForAntipl(ParticipantDTO.fromEntity(defense.getResponsibleForAntiplagiarism())); + } + + if (defense.getResponsibleForNormcontrol() != null) { + dto.setResponsibleForNorm(ParticipantDTO.fromEntity(defense.getResponsibleForNormcontrol())); + } + + if (defense.getDirectionOfPreparation() != null) { + dto.setPreparationDirection(PreparationDirectionDTO.from(defense.getDirectionOfPreparation())); + } + + dto.setStatus(defense.getStatus()); + dto.setGroups(defense.getGroups().stream().map(g -> GroupDTO.from(g, true)).toList()); + dto.setDefenseDate(defense.getDefenseDate()); + dto.setCreatedAt(defense.getAuditInfo().getCreatedAt()); + dto.setUpdatedAt(defense.getAuditInfo().getUpdatedAt()); + return dto; + } +} diff --git a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/DiplomaTopicDTO.java b/server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/DiplomaTopicDTO.java similarity index 95% rename from server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/DiplomaTopicDTO.java rename to server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/DiplomaTopicDTO.java index 33f72a6..9c71749 100644 --- a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/DiplomaTopicDTO.java +++ b/server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/DiplomaTopicDTO.java @@ -1,4 +1,4 @@ -package ru.mskobaro.tdms.presentation.controller.payload; +package ru.mskobaro.tdms.integration.controller.payload; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/ErrorDTO.java b/server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/ErrorDTO.java similarity index 90% rename from server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/ErrorDTO.java rename to server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/ErrorDTO.java index d099897..e0bebf1 100644 --- a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/ErrorDTO.java +++ b/server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/ErrorDTO.java @@ -1,4 +1,4 @@ -package ru.mskobaro.tdms.presentation.controller.payload; +package ru.mskobaro.tdms.integration.controller.payload; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/GroupDTO.java b/server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/GroupDTO.java similarity index 91% rename from server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/GroupDTO.java rename to server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/GroupDTO.java index b8fcf21..3edb426 100644 --- a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/GroupDTO.java +++ b/server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/GroupDTO.java @@ -1,4 +1,4 @@ -package ru.mskobaro.tdms.presentation.controller.payload; +package ru.mskobaro.tdms.integration.controller.payload; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.Pattern; @@ -30,9 +30,7 @@ public class GroupDTO { dto.setId(group.getId()); dto.setName(group.getName()); if (includeStudents && group.getStudents() != null) { - dto.setStudents( - group.getStudents() - .stream() + dto.setStudents(group.getStudents().stream() .filter(sd -> !sd.getParticipant().isDeleted()) .map(sd -> StudentDataDTO.from(sd, false)) .toList()); diff --git a/server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/IdDTO.java b/server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/IdDTO.java new file mode 100644 index 0000000..472e4f3 --- /dev/null +++ b/server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/IdDTO.java @@ -0,0 +1,20 @@ +package ru.mskobaro.tdms.integration.controller.payload; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@NoArgsConstructor +@Getter +@Setter +@ToString +public class IdDTO { + private Long id; + + public static IdDTO fromId(Long id) { + IdDTO dto = new IdDTO(); + dto.setId(id); + return dto; + } +} diff --git a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/LoginDTO.java b/server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/LoginDTO.java similarity index 94% rename from server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/LoginDTO.java rename to server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/LoginDTO.java index 1fcf3eb..906cc32 100644 --- a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/LoginDTO.java +++ b/server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/LoginDTO.java @@ -1,4 +1,4 @@ -package ru.mskobaro.tdms.presentation.controller.payload; +package ru.mskobaro.tdms.integration.controller.payload; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.Pattern; diff --git a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/ParticipantDTO.java b/server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/ParticipantDTO.java similarity index 81% rename from server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/ParticipantDTO.java rename to server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/ParticipantDTO.java index fc7f4e9..0f05fb0 100644 --- a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/ParticipantDTO.java +++ b/server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/ParticipantDTO.java @@ -1,20 +1,15 @@ -package ru.mskobaro.tdms.presentation.controller.payload; +package ru.mskobaro.tdms.integration.controller.payload; -import com.fasterxml.jackson.annotation.JsonIdentityInfo; -import com.fasterxml.jackson.annotation.ObjectIdGenerators; import lombok.Getter; import lombok.Setter; -import lombok.ToString; import ru.mskobaro.tdms.business.entity.Participant; import java.time.LocalDateTime; -import java.time.ZonedDateTime; import java.util.List; @Getter @Setter -@ToString -@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") +// @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") public class ParticipantDTO { private Long id; private String firstName; diff --git a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/ParticipantSaveDTO.java b/server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/ParticipantSaveDTO.java similarity index 81% rename from server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/ParticipantSaveDTO.java rename to server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/ParticipantSaveDTO.java index a9b5fbb..a4a919c 100644 --- a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/ParticipantSaveDTO.java +++ b/server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/ParticipantSaveDTO.java @@ -1,4 +1,4 @@ -package ru.mskobaro.tdms.presentation.controller.payload; +package ru.mskobaro.tdms.integration.controller.payload; import jakarta.validation.Valid; import jakarta.validation.constraints.*; @@ -33,6 +33,8 @@ public class ParticipantSaveDTO { private StudentRegistrationDTO studentData; @Valid private TeacherRegistrationDTO teacherData; + @Valid + private CommMemDto commMemData; @Getter @ToString @@ -51,6 +53,19 @@ public class ParticipantSaveDTO { private Long groupId; private Long curatorId; private Long diplomaTopicId; + private Boolean electronic; + private Integer otziv; + private Integer preDefenseMark; + private Boolean normcontrol; + private Integer antiplagiarism; + private Boolean zachetka; + private String work; + private Boolean vnedreniye; + private Boolean otlichiye; + private Boolean magistracy; + private Long marks3; + private Long marks4; + private Long marks5; } @Getter @@ -61,4 +76,11 @@ public class ParticipantSaveDTO { private String degree; } + @Getter + @ToString + public static class CommMemDto { + private String workPlace; + private String workPosition; + } + } \ No newline at end of file diff --git a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/PreparationDirectionDTO.java b/server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/PreparationDirectionDTO.java similarity index 77% rename from server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/PreparationDirectionDTO.java rename to server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/PreparationDirectionDTO.java index 7568ed4..8e5921b 100644 --- a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/PreparationDirectionDTO.java +++ b/server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/PreparationDirectionDTO.java @@ -1,4 +1,4 @@ -package ru.mskobaro.tdms.presentation.controller.payload; +package ru.mskobaro.tdms.integration.controller.payload; import lombok.Getter; import lombok.NoArgsConstructor; @@ -14,6 +14,7 @@ public class PreparationDirectionDTO { private Long id; private String name; private String code; + private TeacherDataDTO responsible; private LocalDateTime createdAt; private LocalDateTime updatedAt; @@ -22,6 +23,9 @@ public class PreparationDirectionDTO { dto.setId(preparationDirection.getId()); dto.setName(preparationDirection.getName()); dto.setCode(preparationDirection.getCode()); + if (preparationDirection.getResponsible() != null) { + dto.setResponsible(TeacherDataDTO.from(preparationDirection.getResponsible())); + } dto.setCreatedAt(preparationDirection.getAuditInfo().getCreatedAt()); dto.setUpdatedAt(preparationDirection.getAuditInfo().getUpdatedAt()); return dto; diff --git a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/RoleDTO.java b/server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/RoleDTO.java similarity index 88% rename from server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/RoleDTO.java rename to server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/RoleDTO.java index 8192de3..66bdc96 100644 --- a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/RoleDTO.java +++ b/server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/RoleDTO.java @@ -1,4 +1,4 @@ -package ru.mskobaro.tdms.presentation.controller.payload; +package ru.mskobaro.tdms.integration.controller.payload; import ru.mskobaro.tdms.business.entity.Participant; diff --git a/server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/StudentDataDTO.java b/server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/StudentDataDTO.java new file mode 100644 index 0000000..fc9a864 --- /dev/null +++ b/server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/StudentDataDTO.java @@ -0,0 +1,88 @@ +package ru.mskobaro.tdms.integration.controller.payload; + +import lombok.Getter; +import lombok.Setter; +import ru.mskobaro.tdms.business.entity.StudentData; + + +// @Column(name = "magistracy_wanted") +// private Boolean magistracy; +// private Boolean electronic; +// +// @Column(name = "oztiv_mark") +// private Integer otziv; +// @Column(name = "predefnese_mark") +// private Integer preDefenseMark; +// +// @Column(name = "normal_control") +// private Boolean normcontrol; +// @Column(name = "anti_plagiarism") +// private Integer antiplagiarism; +// +// @Column(name = "record_book_returned") +// private Boolean zachetka; +// +// @Column(name = "work") +// private String work; +// +// @Column(name = "vnedreniye") +// private Boolean vnedreniye; +// @Column(name = "diploma_with_honors") +// private Boolean otlichiye; + + +@Setter +@Getter +public class StudentDataDTO { + private Long id; + private GroupDTO group; + private ParticipantDTO participant; + private TeacherDataDTO curator; + private DiplomaTopicDTO diplomaTopic; + private Boolean magistracy; + private Boolean electronic; + private Integer otziv; + private Integer preDefenseMark; + private Boolean normcontrol; + private Integer antiplagiarism; + private Boolean zachetka; + private String work; + private Boolean vnedreniye; + private Boolean otlichiye; + private Integer protDay; + private Integer protOrder; + private Long marks5; + private Long marks4; + private Long marks3; + + public static StudentDataDTO from(StudentData studentData, boolean includeGroup) { + StudentDataDTO dto = new StudentDataDTO(); + dto.setId(studentData.getId()); + if (includeGroup && studentData.getGroup() != null) { + dto.setGroup(GroupDTO.from(studentData.getGroup(), false)); + } + dto.setParticipant(ParticipantDTO.fromEntity(studentData.getParticipant())); + if (studentData.getCurator() != null) { + dto.setCurator(TeacherDataDTO.from(studentData.getCurator())); + } + if (studentData.getDiplomaTopic() != null) { + dto.setDiplomaTopic(DiplomaTopicDTO.from(studentData.getDiplomaTopic())); + } + dto.setMagistracy(studentData.getMagistracy()); + dto.setElectronic(studentData.getElectronic()); + dto.setOtziv(studentData.getOtziv()); + dto.setPreDefenseMark(studentData.getPreDefenseMark()); + dto.setNormcontrol(studentData.getNormcontrol()); + dto.setAntiplagiarism(studentData.getAntiplagiarism()); + dto.setZachetka(studentData.getZachetka()); + dto.setWork(studentData.getWork()); + dto.setVnedreniye(studentData.getVnedreniye()); + dto.setOtlichiye(studentData.getOtlichiye()); + dto.setProtDay(studentData.getProtectionDay()); + dto.setProtOrder(studentData.getProtectionOrder()); + dto.setMarks5(studentData.getMarks5()); + dto.setMarks4(studentData.getMarks4()); + dto.setMarks3(studentData.getMarks3()); + return dto; + } +} diff --git a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/TeacherDataDTO.java b/server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/TeacherDataDTO.java similarity index 93% rename from server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/TeacherDataDTO.java rename to server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/TeacherDataDTO.java index f79ebe1..e1f513d 100644 --- a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/TeacherDataDTO.java +++ b/server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/TeacherDataDTO.java @@ -1,4 +1,4 @@ -package ru.mskobaro.tdms.presentation.controller.payload; +package ru.mskobaro.tdms.integration.controller.payload; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/TopicAgreementDto.java b/server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/TopicAgreementDto.java new file mode 100644 index 0000000..82f0038 --- /dev/null +++ b/server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/TopicAgreementDto.java @@ -0,0 +1,39 @@ +package ru.mskobaro.tdms.integration.controller.payload; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import ru.mskobaro.tdms.business.entity.Task; +import ru.mskobaro.tdms.business.entity.TopicAgreementTask; + +import java.time.LocalDateTime; + +@NoArgsConstructor +@Getter +@Setter +public class TopicAgreementDto { + private Long id; + private Task.Status status; + private Long makerParticId; + private Long checkerParticId; + private Long diplomaTopicId; + private String diplomaTopicName; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + private LocalDateTime approvedAt; + + + public static TopicAgreementDto from(TopicAgreementTask task) { + TopicAgreementDto dto = new TopicAgreementDto(); + dto.setId(task.getId()); + dto.setStatus(task.getStatus()); + dto.setMakerParticId(task.getMakerParticId()); + dto.setCheckerParticId(task.getCheckerParticId()); + dto.setDiplomaTopicId(task.getDiplomaTopicId()); + dto.setDiplomaTopicName(task.getDiplomaTopicName()); + dto.setCreatedAt(task.getAuditInfo().getCreatedAt()); + dto.setUpdatedAt(task.getAuditInfo().getUpdatedAt()); + dto.setApprovedAt(task.getApprovedAt()); + return dto; + } +} diff --git a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/UserDTO.java b/server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/UserDTO.java similarity index 93% rename from server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/UserDTO.java rename to server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/UserDTO.java index 59df214..d1d7302 100644 --- a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/UserDTO.java +++ b/server/src/main/java/ru/mskobaro/tdms/integration/controller/payload/UserDTO.java @@ -1,4 +1,4 @@ -package ru.mskobaro.tdms.presentation.controller.payload; +package ru.mskobaro.tdms.integration.controller.payload; import com.fasterxml.jackson.annotation.JsonIdentityInfo; @@ -7,7 +7,6 @@ import lombok.Builder; import ru.mskobaro.tdms.business.entity.User; import java.time.LocalDateTime; -import java.time.ZonedDateTime; @Builder diff --git a/server/src/main/java/ru/mskobaro/tdms/integration/database/CommissionMemberRepository.java b/server/src/main/java/ru/mskobaro/tdms/integration/database/CommissionMemberRepository.java new file mode 100644 index 0000000..4d01a99 --- /dev/null +++ b/server/src/main/java/ru/mskobaro/tdms/integration/database/CommissionMemberRepository.java @@ -0,0 +1,17 @@ +package ru.mskobaro.tdms.integration.database; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import ru.mskobaro.tdms.business.entity.CommissionMemberData; +import ru.mskobaro.tdms.business.exception.NotFoundException; + +@Repository +public interface CommissionMemberRepository extends JpaRepository { + default CommissionMemberData findByIdThrow(Long id) { + return findById(id).orElseThrow(() -> new NotFoundException(CommissionMemberData.class, id)); + } + + boolean existsByParticipant_Id(Long id); + + CommissionMemberData findByParticipant_Id(Long participantId); +} diff --git a/server/src/main/java/ru/mskobaro/tdms/integration/database/DefenceRepository.java b/server/src/main/java/ru/mskobaro/tdms/integration/database/DefenceRepository.java deleted file mode 100644 index 5736206..0000000 --- a/server/src/main/java/ru/mskobaro/tdms/integration/database/DefenceRepository.java +++ /dev/null @@ -1,25 +0,0 @@ -package ru.mskobaro.tdms.integration.database; - -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.stereotype.Repository; -import ru.mskobaro.tdms.business.entity.Defense; -import ru.mskobaro.tdms.business.exception.NotFoundException; - -import java.util.Optional; - -@Repository -public interface DefenceRepository extends JpaRepository { - default Defense findByIdThrow(Long id) { - return this.findById(id).orElseThrow(() -> new NotFoundException(Defense.class, id)); - } - - @Override - @Query("SELECT d from Defense d " + - "left join fetch d.bestWorks bw " + - "left join fetch d.commissionMembers cm " + - "where d.id = :id " + - "and bw.participant.deleted = false " + - "and cm.participant.deleted = false") - Optional findById(Long id); -} diff --git a/server/src/main/java/ru/mskobaro/tdms/integration/database/DefenseRepository.java b/server/src/main/java/ru/mskobaro/tdms/integration/database/DefenseRepository.java new file mode 100644 index 0000000..bed8592 --- /dev/null +++ b/server/src/main/java/ru/mskobaro/tdms/integration/database/DefenseRepository.java @@ -0,0 +1,13 @@ +package ru.mskobaro.tdms.integration.database; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import ru.mskobaro.tdms.business.entity.Defense; +import ru.mskobaro.tdms.business.exception.NotFoundException; + +@Repository +public interface DefenseRepository extends JpaRepository { + default Defense findByIdThrow(Long id) { + return this.findById(id).orElseThrow(() -> new NotFoundException(Defense.class, id)); + } +} diff --git a/server/src/main/java/ru/mskobaro/tdms/integration/database/DiplomaTopicRepository.java b/server/src/main/java/ru/mskobaro/tdms/integration/database/DiplomaTopicRepository.java index c9a6f20..1628484 100644 --- a/server/src/main/java/ru/mskobaro/tdms/integration/database/DiplomaTopicRepository.java +++ b/server/src/main/java/ru/mskobaro/tdms/integration/database/DiplomaTopicRepository.java @@ -4,8 +4,6 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; import ru.mskobaro.tdms.business.entity.DiplomaTopic; -import ru.mskobaro.tdms.business.entity.DirectionOfPreparation; -import ru.mskobaro.tdms.business.entity.StudentData; import ru.mskobaro.tdms.business.exception.NotFoundException; import java.util.List; @@ -17,13 +15,9 @@ public interface DiplomaTopicRepository extends JpaRepository new NotFoundException(DiplomaTopic.class, id)); } - @Override - @Query("SELECT d FROM DiplomaTopic d WHERE d.id = :id AND d.teacher.participant.deleted = false") - Optional findById(Long id); - @Query("SELECT d FROM DiplomaTopic d " + "inner join d.directionOfPreparation dp " + - "inner join StudentData sd on sd.id = :studentIdwhere " + - "where dp = sd.group.directionOfPreparation") - List findAllForStudentId(Long studentId); + "inner join StudentData sd on sd.participant.id = :particId " + + "where dp = sd.group.directionOfPreparation and dp is not null and sd.curator = d.teacher and d.teacher is not null") + List findAllForStudentByParticId(Long particId); } \ No newline at end of file diff --git a/server/src/main/java/ru/mskobaro/tdms/integration/database/MessageTemplateRepository.java b/server/src/main/java/ru/mskobaro/tdms/integration/database/MessageTemplateRepository.java new file mode 100644 index 0000000..b3ff238 --- /dev/null +++ b/server/src/main/java/ru/mskobaro/tdms/integration/database/MessageTemplateRepository.java @@ -0,0 +1,15 @@ +package ru.mskobaro.tdms.integration.database; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import ru.mskobaro.tdms.business.entity.MessageTemplate; +import ru.mskobaro.tdms.business.exception.NotFoundException; + +@Repository +public interface MessageTemplateRepository extends JpaRepository { + default MessageTemplate findByIdThrow(Long id) { + return findById(id).orElseThrow(() -> new NotFoundException(MessageTemplate.class, id)); + } + + MessageTemplate findByMessageType(MessageTemplate.MessageType messageType); +} diff --git a/server/src/main/java/ru/mskobaro/tdms/integration/database/ParticipantRepository.java b/server/src/main/java/ru/mskobaro/tdms/integration/database/ParticipantRepository.java index 3fde395..d2f6de3 100644 --- a/server/src/main/java/ru/mskobaro/tdms/integration/database/ParticipantRepository.java +++ b/server/src/main/java/ru/mskobaro/tdms/integration/database/ParticipantRepository.java @@ -24,4 +24,6 @@ public interface ParticipantRepository extends JpaRepository @Override @Query("SELECT p from Participant p where p.deleted = false") List findAll(); + + boolean existsByNumberPhoneOrEmail(String numberPhone, String email); } diff --git a/server/src/main/java/ru/mskobaro/tdms/integration/database/TaskRepository.java b/server/src/main/java/ru/mskobaro/tdms/integration/database/TaskRepository.java index 7d61ab8..a3583f0 100644 --- a/server/src/main/java/ru/mskobaro/tdms/integration/database/TaskRepository.java +++ b/server/src/main/java/ru/mskobaro/tdms/integration/database/TaskRepository.java @@ -1,21 +1,13 @@ package ru.mskobaro.tdms.integration.database; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; import ru.mskobaro.tdms.business.entity.Task; import ru.mskobaro.tdms.business.exception.NotFoundException; -import java.util.List; - @Repository public interface TaskRepository extends JpaRepository { default Task findByIdThrow(Long id) { return findById(id).orElseThrow(() -> new NotFoundException(Task.class, id)); } - - @Query(value = "SELECT t FROM task t " + - "WHERE t.type = :type " + - "and t.fields->>'makerParticipantId' = :id", nativeQuery = true) - List findDiplomaTopicAgreementTaskByMakerId(Long id, Task.Type type); } diff --git a/server/src/main/java/ru/mskobaro/tdms/presentation/exception/ApplicationExceptionHandler.java b/server/src/main/java/ru/mskobaro/tdms/integration/exception/ApplicationExceptionHandler.java similarity index 78% rename from server/src/main/java/ru/mskobaro/tdms/presentation/exception/ApplicationExceptionHandler.java rename to server/src/main/java/ru/mskobaro/tdms/integration/exception/ApplicationExceptionHandler.java index 84cbeb3..a2eba66 100644 --- a/server/src/main/java/ru/mskobaro/tdms/presentation/exception/ApplicationExceptionHandler.java +++ b/server/src/main/java/ru/mskobaro/tdms/integration/exception/ApplicationExceptionHandler.java @@ -1,5 +1,6 @@ -package ru.mskobaro.tdms.presentation.exception; +package ru.mskobaro.tdms.integration.exception; +import jakarta.mail.SendFailedException; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.context.support.DefaultMessageSourceResolvable; @@ -11,14 +12,15 @@ import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.servlet.resource.NoResourceFoundException; import ru.mskobaro.tdms.business.exception.AccessDeniedException; import ru.mskobaro.tdms.business.exception.BusinessException; -import ru.mskobaro.tdms.presentation.controller.payload.ErrorDTO; +import ru.mskobaro.tdms.integration.controller.payload.ErrorDTO; +import java.util.Arrays; import java.util.UUID; import java.util.stream.Collectors; -import static org.springframework.http.HttpStatus.*; import static org.springframework.http.HttpStatus.NOT_FOUND; -import static ru.mskobaro.tdms.presentation.controller.payload.ErrorDTO.ErrorCode.*; +import static org.springframework.http.HttpStatus.*; +import static ru.mskobaro.tdms.integration.controller.payload.ErrorDTO.ErrorCode.*; @RestControllerAdvice @Slf4j @@ -35,7 +37,7 @@ public class ApplicationExceptionHandler { @ExceptionHandler(BusinessException.class) public ErrorDTO handleBusinessException(BusinessException e, HttpServletResponse response) { - log.warn("Business error: {}", e.getMessage()); + log.warn("Business error: ", e); response.setStatus(e.getErrorCode().getHttpStatus().value()); return new ErrorDTO(e.getMessage(), e.getErrorCode()); } @@ -70,4 +72,18 @@ public class ApplicationExceptionHandler { log.error("Unexpected exception ({})", uuid, e); return new ErrorDTO("Идентификатор ошибки: (" + uuid + ")\nПроизошла непредвиденная ошибка, обратитесь к администратору", INTERNAL_ERROR); } + + @ExceptionHandler(SendFailedException.class) + @ResponseStatus(BAD_REQUEST) + public ErrorDTO handleSendFailedException(SendFailedException e) { + log.error("Send failed: {}", e.getMessage()); + StringBuilder addresses = new StringBuilder(); + Arrays.stream(e.getInvalidAddresses()).forEach(address -> { + if (!addresses.isEmpty()) { + addresses.append(", "); + } + addresses.append(address); + }); + return new ErrorDTO("Ошибка отправки Email-сообщения (%s)".formatted(addresses), BUSINESS_ERROR); + } } diff --git a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/DefenceController.java b/server/src/main/java/ru/mskobaro/tdms/presentation/controller/DefenceController.java deleted file mode 100644 index 0ef7a5c..0000000 --- a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/DefenceController.java +++ /dev/null @@ -1,22 +0,0 @@ -package ru.mskobaro.tdms.presentation.controller; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import ru.mskobaro.tdms.business.service.DefenceService; -import ru.mskobaro.tdms.presentation.controller.payload.DefenceDTO; - -import java.util.List; - -@RestController -@RequestMapping("/api/v1/defence") -public class DefenceController { - @Autowired - private DefenceService defenceService; - - @GetMapping("/all") - public List getAllDefences() { - return defenceService.getAllDefences(); - } -} diff --git a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/DiplomaTopicController.java b/server/src/main/java/ru/mskobaro/tdms/presentation/controller/DiplomaTopicController.java deleted file mode 100644 index 7294156..0000000 --- a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/DiplomaTopicController.java +++ /dev/null @@ -1,34 +0,0 @@ -package ru.mskobaro.tdms.presentation.controller; - - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; -import ru.mskobaro.tdms.business.service.DiplomaTopicService; -import ru.mskobaro.tdms.presentation.controller.payload.DiplomaTopicDTO; - -import java.util.List; - - -@RestController -@RequestMapping("/api/v1/diploma-topic/") -@Validated -public class DiplomaTopicController { - @Autowired - private DiplomaTopicService diplomaTopicService; - - @GetMapping("/all") - public List getAll() { - return diplomaTopicService.findAll().stream().map(DiplomaTopicDTO::from).toList(); - } - - @PostMapping("/save") - public void save(@RequestBody DiplomaTopicDTO diplomaTopicDTO) { - diplomaTopicService.save(diplomaTopicDTO); - } - - @GetMapping("/all-for-student") - public List getAllForStudent(@RequestParam Long studentId) { - return diplomaTopicService.findAllForStudent(studentId).stream().map(DiplomaTopicDTO::from).toList(); - } -} diff --git a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/TaskController.java b/server/src/main/java/ru/mskobaro/tdms/presentation/controller/TaskController.java deleted file mode 100644 index 8266edf..0000000 --- a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/TaskController.java +++ /dev/null @@ -1,34 +0,0 @@ -package ru.mskobaro.tdms.presentation.controller; - -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.*; -import ru.mskobaro.tdms.business.service.TaskService; -import ru.mskobaro.tdms.presentation.controller.payload.TaskDto; - -@RestController -@RequestMapping("/api/v1/task") -public class TaskController { - @Autowired - private TaskService taskService; - - @GetMapping("/diploma-agreement-maker") - public TaskDto diplomaTopicAgreementMaker() { - return TaskDto.from(taskService.findDiplomaTopicAgreementTaskCallerMaker()); - } - - @PostMapping("/create-topic-agreement") - public void createDiplomaTopicAgreement(@RequestBody DiplomaTopicAgreementDTO diplomaTopicAgreementDTO) { - taskService.createDiplomaAgreementTask(diplomaTopicAgreementDTO); - } - - @NoArgsConstructor - @Getter - @Setter - public static class DiplomaTopicAgreementDTO { - private String diplomaTopicName; - private Long diplomaTopicId; - } -} diff --git a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/DefenceDTO.java b/server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/DefenceDTO.java deleted file mode 100644 index 767267d..0000000 --- a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/DefenceDTO.java +++ /dev/null @@ -1,39 +0,0 @@ -package ru.mskobaro.tdms.presentation.controller.payload; - -import lombok.Getter; -import lombok.Setter; -import org.apache.commons.collections4.CollectionUtils; -import ru.mskobaro.tdms.business.entity.Defense; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.stream.Collectors; - -@Getter -@Setter -public class DefenceDTO { - private Long id; - private List commissionMembers; - private List groups; - - private LocalDateTime createdAt; - private LocalDateTime updatedAt; - - public static DefenceDTO from(Defense defense) { - DefenceDTO dto = new DefenceDTO(); - dto.setId(defense.getId()); - - if (CollectionUtils.isNotEmpty(defense.getCommissionMembers())) { - dto.setCommissionMembers( - defense.getCommissionMembers().stream() - .map(CommissionMemberDTO::from) - .collect(Collectors.toList()) - ); - } - - dto.setGroups(defense.getGroups().stream().map(g -> GroupDTO.from(g, true)).toList()); - dto.setCreatedAt(defense.getAuditInfo().getCreatedAt()); - dto.setUpdatedAt(defense.getAuditInfo().getUpdatedAt()); - return dto; - } -} diff --git a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/IdDto.java b/server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/IdDto.java deleted file mode 100644 index 98fd6cc..0000000 --- a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/IdDto.java +++ /dev/null @@ -1,14 +0,0 @@ -package ru.mskobaro.tdms.presentation.controller.payload; - -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import lombok.ToString; - -@NoArgsConstructor -@Getter -@Setter -@ToString -public class IdDto { - private Long id; -} diff --git a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/StudentDataDTO.java b/server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/StudentDataDTO.java deleted file mode 100644 index bdd2173..0000000 --- a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/StudentDataDTO.java +++ /dev/null @@ -1,31 +0,0 @@ -package ru.mskobaro.tdms.presentation.controller.payload; - -import lombok.Getter; -import lombok.Setter; -import ru.mskobaro.tdms.business.entity.StudentData; - -@Setter -@Getter -public class StudentDataDTO { - private Long id; - private GroupDTO group; - private ParticipantDTO participant; - private TeacherDataDTO curator; - private DiplomaTopicDTO diplomaTopic; - - public static StudentDataDTO from(StudentData studentData, boolean includeGroup) { - StudentDataDTO dto = new StudentDataDTO(); - dto.setId(studentData.getId()); - if (includeGroup && studentData.getGroup() != null) { - dto.setGroup(GroupDTO.from(studentData.getGroup(), false)); - } - dto.setParticipant(ParticipantDTO.fromEntity(studentData.getParticipant())); - if (studentData.getCurator() != null) { - dto.setCurator(TeacherDataDTO.from(studentData.getCurator())); - } - if (studentData.getDiplomaTopic() != null) { - dto.setDiplomaTopic(DiplomaTopicDTO.from(studentData.getDiplomaTopic())); - } - return dto; - } -} diff --git a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/TaskDto.java b/server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/TaskDto.java deleted file mode 100644 index feba773..0000000 --- a/server/src/main/java/ru/mskobaro/tdms/presentation/controller/payload/TaskDto.java +++ /dev/null @@ -1,33 +0,0 @@ -package ru.mskobaro.tdms.presentation.controller.payload; - -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import ru.mskobaro.tdms.business.entity.Task; -import ru.mskobaro.tdms.business.taskfields.TaskFields; - -import java.time.LocalDateTime; - -@NoArgsConstructor -@Getter -@Setter -public class TaskDto { - private Long id; - private Task.Type type; - private Task.Status status; - private TaskFields fields; - private LocalDateTime createdAt; - private LocalDateTime updatedAt; - - - public static TaskDto from(Task task) { - TaskDto dto = new TaskDto(); - dto.setId(task.getId()); - dto.setType(task.getType()); - dto.setStatus(task.getStatus()); - dto.setFields(task.getFields()); - dto.setCreatedAt(task.getAuditInfo().getCreatedAt()); - dto.setUpdatedAt(task.getAuditInfo().getUpdatedAt()); - return dto; - } -} diff --git a/server/src/main/java/ru/mskobaro/tdms/system/config/MailConfig.java b/server/src/main/java/ru/mskobaro/tdms/system/config/MailConfig.java new file mode 100644 index 0000000..5b4e277 --- /dev/null +++ b/server/src/main/java/ru/mskobaro/tdms/system/config/MailConfig.java @@ -0,0 +1,27 @@ +package ru.mskobaro.tdms.system.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.JavaMailSenderImpl; + +import java.util.Properties; + +@Configuration +public class MailConfig { + + @Bean + public JavaMailSender getJavaMailSender( + @Value("${application.smtp.host}") String smtpHost, + @Value("${application.smtp.port}") String smtpPort + ) { + JavaMailSenderImpl mailSender = new JavaMailSenderImpl(); + mailSender.setHost(smtpHost); + mailSender.setPort(Integer.parseInt(smtpPort)); + Properties props = mailSender.getJavaMailProperties(); + props.put("mail.transport.protocol", "smtp"); + props.put("mail.debug", "true"); + return mailSender; + } +} diff --git a/server/src/main/java/ru/mskobaro/tdms/system/config/SecurityConfig.java b/server/src/main/java/ru/mskobaro/tdms/system/config/SecurityConfig.java index 78d945e..ece3b9e 100644 --- a/server/src/main/java/ru/mskobaro/tdms/system/config/SecurityConfig.java +++ b/server/src/main/java/ru/mskobaro/tdms/system/config/SecurityConfig.java @@ -31,8 +31,7 @@ import ru.mskobaro.tdms.system.web.LoggingRequestFilter; import java.time.Duration; import java.util.List; -import static ru.mskobaro.tdms.business.service.RoleService.Authority.ADMIN; -import static ru.mskobaro.tdms.business.service.RoleService.Authority.SECRETARY; +import static ru.mskobaro.tdms.business.service.RoleService.Authority.*; @Slf4j @@ -115,6 +114,8 @@ public class SecurityConfig { // Сложная логика авторизации, так что проверяем явно в ParticipantService httpAuthorization.requestMatchers("/api/v1/participant/save").authenticated(); httpAuthorization.requestMatchers("/api/v1/participant/delete").hasAuthority(ADMIN.getAuthority()); + httpAuthorization.requestMatchers("/api/v1/participant/get-all-antipl").permitAll(); + httpAuthorization.requestMatchers("/api/v1/participant/get-all-norm").permitAll(); httpAuthorization.requestMatchers("/api/v1/group/get-all-groups").permitAll(); httpAuthorization.requestMatchers("/api/v1/group/save").hasAnyAuthority(ADMIN.getAuthority(), SECRETARY.getAuthority()); @@ -122,6 +123,8 @@ public class SecurityConfig { httpAuthorization.requestMatchers("/api/v1/student/by-partic-id").permitAll(); httpAuthorization.requestMatchers("/api/v1/student/all-without-group").permitAll(); + httpAuthorization.requestMatchers("/api/v1/student/by-id").permitAll(); + httpAuthorization.requestMatchers("/api/v1/student/by-defense-id").permitAll(); httpAuthorization.requestMatchers("/api/v1/teacher-data/get-all").permitAll(); httpAuthorization.requestMatchers("/api/v1/teacher-data/by-partic-id").permitAll(); @@ -129,11 +132,39 @@ public class SecurityConfig { httpAuthorization.requestMatchers("/api/v1/prep-direction/get-all").permitAll(); httpAuthorization.requestMatchers("/api/v1/prep-direction/save").hasAnyAuthority(ADMIN.getAuthority(), SECRETARY.getAuthority()); - httpAuthorization.requestMatchers("/api/v1/defence/all").permitAll(); + httpAuthorization.requestMatchers("/api/v1/defense/get-all").permitAll(); + httpAuthorization.requestMatchers("/api/v1/defense/get-by-id").permitAll(); + httpAuthorization.requestMatchers("/api/v1/defense/save").permitAll(); + httpAuthorization.requestMatchers("/api/v1/defense/defense-table").permitAll(); + httpAuthorization.requestMatchers("/api/v1/defense/defense-table-save").hasAnyAuthority(ADMIN.getAuthority(), TEACHER.getAuthority()); + httpAuthorization.requestMatchers("/api/v1/defense/to-next-state").hasAnyAuthority(ADMIN.getAuthority(), SECRETARY.getAuthority(), TEACHER.getAuthority()); + httpAuthorization.requestMatchers("api/v1/defense/recreate-protection-order").hasAnyAuthority(ADMIN.getAuthority(), TEACHER.getAuthority()); + httpAuthorization.requestMatchers("api/v1/defense/save-order").hasAnyAuthority(ADMIN.getAuthority(), TEACHER.getAuthority()); + + httpAuthorization.requestMatchers("/api/v1/task/create-topic-agreement").hasAuthority(STUDENT.getAuthority()); + httpAuthorization.requestMatchers("/api/v1/task/diploma-agreement-maker").hasAuthority(STUDENT.getAuthority()); + httpAuthorization.requestMatchers("/api/v1/task/notif-count").authenticated(); + httpAuthorization.requestMatchers("/api/v1/task/get-current-tasks").authenticated(); + httpAuthorization.requestMatchers("/api/v1/task/diploma-agreement-maker-by-partic-id").authenticated(); + httpAuthorization.requestMatchers("/api/v1/task/topic-preparation-finish").hasAnyAuthority(TEACHER.getAuthority()); + httpAuthorization.requestMatchers("/api/v1/task/agreement-change").hasAnyAuthority(STUDENT.getAuthority(), TEACHER.getAuthority()); + httpAuthorization.requestMatchers("/api/v1/task/def-prep-finish").hasAnyAuthority(TEACHER.getAuthority()); + httpAuthorization.requestMatchers("/api/v1/task/norm-antipl-finish").authenticated(); httpAuthorization.requestMatchers("/api/v1/diploma-topic/all").permitAll(); httpAuthorization.requestMatchers("/api/v1/diploma-topic/all-for-student").permitAll(); - httpAuthorization.requestMatchers("/api/v1/diploma-topic/save").hasAnyAuthority(ADMIN.getAuthority(), SECRETARY.getAuthority()); + httpAuthorization.requestMatchers("/api/v1/diploma-topic/by-id").permitAll(); + httpAuthorization.requestMatchers("/api/v1/diploma-topic/save").hasAnyAuthority(ADMIN.getAuthority(), SECRETARY.getAuthority(), TEACHER.getAuthority()); + httpAuthorization.requestMatchers("/api/v1/diploma-topic/matching-teacher-and-dir-prep").permitAll(); + + httpAuthorization.requestMatchers("/api/v1/home-page/default-info").permitAll(); + + httpAuthorization.requestMatchers("api/v1/commission-member/get-all").permitAll(); + httpAuthorization.requestMatchers("api/v1/commission-member/by-partic-id").permitAll(); + + httpAuthorization.requestMatchers("api/v1/doc/gek-questions").permitAll(); + httpAuthorization.requestMatchers("api/v1/doc/stud-list").permitAll(); + httpAuthorization.requestMatchers("api/v1/doc/topic-list").permitAll(); httpAuthorization.requestMatchers("/api/**").denyAll(); diff --git a/server/src/main/java/ru/mskobaro/tdms/system/web/LoggingRequestFilter.java b/server/src/main/java/ru/mskobaro/tdms/system/web/LoggingRequestFilter.java index 01397de..3a07c4e 100644 --- a/server/src/main/java/ru/mskobaro/tdms/system/web/LoggingRequestFilter.java +++ b/server/src/main/java/ru/mskobaro/tdms/system/web/LoggingRequestFilter.java @@ -11,10 +11,13 @@ import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; @Slf4j public class LoggingRequestFilter extends OncePerRequestFilter { + @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { UUID uuid = UUID.randomUUID(); @@ -22,6 +25,12 @@ public class LoggingRequestFilter extends OncePerRequestFilter { String username = SecurityContextHolder.getContext().getAuthentication() != null ? SecurityContextHolder.getContext().getAuthentication().getName() : "anonymousUser"; HttpSession session = request.getSession(false); + + if (request.getRequestURI().contains("notif-count") || request.getRequestURI().contains("get-current-tasks")) { + filterChain.doFilter(request, response); + return; + } + log.info("Request received: [{}] {} user: {}, session: {}, remote ip: {} [{}]", request.getMethod(), request.getRequestURI(), username, session == null ? "no" : session.getId(), request.getRemoteAddr(), uuid); diff --git a/server/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/server/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 4535a23..1201cb1 100644 --- a/server/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/server/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -49,5 +49,20 @@ "name": "application.protocol", "type": "java.lang.String", "description": "Service protocol." + }, + { + "name": "application.smtp.host", + "type": "java.lang.String", + "description": "SMTP server host." + }, + { + "name": "application.smtp.port", + "type": "java.lang.String", + "description": "SMTP server port." + }, + { + "name": "application.smtp.message-from", + "type": "java.lang.String", + "description": "Sender email." } ] } \ No newline at end of file diff --git a/server/src/main/resources/application.yml b/server/src/main/resources/application.yml index 2649fb3..6934612 100644 --- a/server/src/main/resources/application.yml +++ b/server/src/main/resources/application.yml @@ -8,8 +8,12 @@ application: version: @version@ type: production port: 443 - domain: tdms.tu-bryansk.ru + domain: vkr.tu-bryansk.ru protocol: https + smtp: + host: localhost + port: 2525 + message-from: admin@vkr.tu-bryansk.ru spring: application: diff --git a/server/src/main/resources/db/migration/V00000__Initial_schema.sql b/server/src/main/resources/db/migration/V00000__Initial_schema.sql index 22f5225..adb257d 100644 --- a/server/src/main/resources/db/migration/V00000__Initial_schema.sql +++ b/server/src/main/resources/db/migration/V00000__Initial_schema.sql @@ -1,15 +1,13 @@ CREATE TABLE defense ( - id BIGSERIAL PRIMARY KEY, - defense_date DATE, - created_at TIMESTAMP WITHOUT TIME ZONE, - updated_at TIMESTAMP WITHOUT TIME ZONE -); - -CREATE TABLE defense_best_student_works -( - defense_id BIGINT, - student_data_id BIGINT + id BIGSERIAL PRIMARY KEY, + defense_date DATE, + status TEXT NOT NULL, + direction_of_preparation_id BIGINT, + responsible_for_antiplagiarism_id BIGINT, + responsible_for_normcontrol_id BIGINT, + created_at TIMESTAMP WITHOUT TIME ZONE, + updated_at TIMESTAMP WITHOUT TIME ZONE ); CREATE TABLE defense_commission @@ -30,11 +28,12 @@ CREATE TABLE diploma_topic CREATE TABLE direction_of_preparation ( - id BIGSERIAL PRIMARY KEY, - name TEXT, - code TEXT, - created_at TIMESTAMP WITHOUT TIME ZONE, - updated_at TIMESTAMP WITHOUT TIME ZONE + id BIGSERIAL PRIMARY KEY, + name TEXT, + code TEXT, + responsible_id BIGINT, + created_at TIMESTAMP WITHOUT TIME ZONE, + updated_at TIMESTAMP WITHOUT TIME ZONE ); CREATE TABLE "group" @@ -73,7 +72,6 @@ CREATE TABLE role authority TEXT ); -/* todo */ CREATE TABLE student_data ( id BIGSERIAL PRIMARY KEY, @@ -84,30 +82,42 @@ CREATE TABLE student_data protection_order INTEGER, protection_day INTEGER, - mark_comment INTEGER, mark_practice INTEGER, + predefnese_mark INTEGER, + oztiv_mark INTEGER, + - predefnese_comment TEXT, normal_control BOOLEAN, anti_plagiarism INTEGER, + record_book_returned BOOLEAN, + work TEXT, + diploma_topic_id BIGINT, adviser_teacher_partic_id BIGINT, group_id BIGINT, + marks_3 BIGINT, marks_4 BIGINT, marks_5 BIGINT, + commission_mark BIGINT, + estimated BOOLEAN, + diploma_with_honors BOOLEAN, + vnedreniye BOOLEAN, + magistracy_recommendation BOOLEAN, magistracy_wanted BOOLEAN, + + electronic BOOLEAN, + created_at TIMESTAMP WITHOUT TIME ZONE, updated_at TIMESTAMP WITHOUT TIME ZONE ); -/* not implemented */ create table questionnaire ( id BIGSERIAL PRIMARY KEY, @@ -125,7 +135,6 @@ create table study_form updated_at TIMESTAMP WITHOUT TIME ZONE ); -/* todo */ create table stud_comment ( id BIGSERIAL PRIMARY KEY, @@ -134,7 +143,6 @@ create table stud_comment updated_at TIMESTAMP WITHOUT TIME ZONE ); -/* todo */ create table student_data_comment ( id BIGSERIAL PRIMARY KEY, @@ -173,14 +181,34 @@ CREATE TABLE "user" updated_at TIMESTAMP WITHOUT TIME ZONE ); + CREATE TABLE task ( - id BIGSERIAL PRIMARY KEY, - type TEXT, - status TEXT, - fields jsonb, - created_at TIMESTAMP WITHOUT TIME ZONE, - updated_at TIMESTAMP WITHOUT TIME ZONE + id BIGSERIAL PRIMARY KEY, + status TEXT, + type TEXT, + defense_id BIGINT, + maker_partic_id BIGINT, + checker_partic_id BIGINT, + task_type TEXT, + diploma_topic_id BIGINT, + diploma_topic_name TEXT, + practice_mark BIGINT, + antiplagiarism_percent BIGINT, + normcontrol_passed BOOLEAN, + approved_at TIMESTAMP WITHOUT TIME ZONE, + created_at TIMESTAMP WITHOUT TIME ZONE, + updated_at TIMESTAMP WITHOUT TIME ZONE +); + +CREATE TABLE message_template +( + id BIGSERIAL PRIMARY KEY, + message_type TEXT, + subject_template TEXT, + message_template TEXT, + created_at TIMESTAMP WITHOUT TIME ZONE, + updated_at TIMESTAMP WITHOUT TIME ZONE ); ALTER TABLE defense_commission @@ -252,12 +280,6 @@ ALTER TABLE direction_of_preparation ALTER TABLE diploma_topic ADD CONSTRAINT UC_DIPLOMA_TOPIC_NAME FOREIGN KEY (direction_of_preparation_id) REFERENCES direction_of_preparation (id) ON DELETE SET NULL ON UPDATE CASCADE; -ALTER TABLE defense_best_student_works - ADD CONSTRAINT FK_DEFENSE_BEST_STUDENT_WORKS_ON_DEFENSE FOREIGN KEY (defense_id) REFERENCES defense (id) ON DELETE CASCADE ON UPDATE CASCADE; - -ALTER TABLE defense_best_student_works - ADD CONSTRAINT FK_DEFENSE_BEST_STUDENT_WORKS_ON_STUDENT_DATA FOREIGN KEY (student_data_id) REFERENCES student_data (id) ON DELETE CASCADE ON UPDATE CASCADE; - ALTER TABLE stud_comment ADD CONSTRAINT UC_STUD_COMMENT_COMMENT UNIQUE (comment); @@ -277,4 +299,19 @@ alter table questionnaire add constraint UC_QUESTIONNAIRE_STUDENT_DATA_ID unique (student_data_id); alter table questionnaire - add constraint FK_QUESTIONNAIRE_STUDENT_DATA foreign key (student_data_id) references student_data (id) on delete cascade on update cascade; \ No newline at end of file + add constraint FK_QUESTIONNAIRE_STUDENT_DATA foreign key (student_data_id) references student_data (id) on delete cascade on update cascade; + +alter table direction_of_preparation + add constraint FK_RESPONSIBLE FOREIGN KEY (responsible_id) REFERENCES teacher_data (id) ON DELETE CASCADE ON UPDATE CASCADE; + +alter table defense + add constraint FK_RESPONSIBLE_NORM FOREIGN KEY (responsible_for_normcontrol_id) REFERENCES participant (id) ON DELETE CASCADE ON UPDATE CASCADE; + +alter table defense + add constraint FK_RESPONSIBLE_ANTIPLAGIARISM FOREIGN KEY (responsible_for_antiplagiarism_id) REFERENCES participant (id) ON DELETE CASCADE ON UPDATE CASCADE; + +alter table task + add constraint FK_DEFENSE FOREIGN KEY (defense_id) REFERENCES defense (id) ON DELETE CASCADE ON UPDATE CASCADE; + +alter table defense + add constraint FK_DIR_OF_PRERP FOREIGN KEY (direction_of_preparation_id) REFERENCES direction_of_preparation (id) ON DELETE CASCADE ON UPDATE CASCADE; \ No newline at end of file diff --git a/server/src/main/resources/db/migration/V00600__Insert_default_message_templates.sql b/server/src/main/resources/db/migration/V00600__Insert_default_message_templates.sql new file mode 100644 index 0000000..d3d2cf9 --- /dev/null +++ b/server/src/main/resources/db/migration/V00600__Insert_default_message_templates.sql @@ -0,0 +1,97 @@ +-- TOPIC_LIST_PREPARE_START_FOR_TEACHER, +-- +-- TOPIC_CHOOSE_FOR_STUDENT, +-- TOPIC_CHOOSE_FOR_TEACHER, +-- +-- TASK_CREATE_FOR_TEACHER, +-- TASK_ASSIGN_FOR_TEACHER, +-- +-- PRE_DIPLOMA_PRACTICE_START_FOR_TEACHER, +-- PRE_DIPLOMA_PRACTICE_START_FOR_STUDENT, +-- +-- TOPIC_FINALIZATION_FOR_TEACHER, +-- +-- PRE_DEFENSE_FOR_STUDENT, +-- +-- ANTIPLAGIARISM_FOR_STUDENT, +-- +-- DEFENSE_PREPARATION_FOR_STUDENT, +-- +-- MAIN_VRK_WORK_START, +-- +-- NORMCONTROL_AND_ANTIPLAGIARISM, +-- +-- DEFENSE, +-- +-- DEFENSE_PREPARATION_FOR_TEACHER, + +INSERT INTO message_template (message_type, + subject_template, + message_template, + created_at, + updated_at) +VALUES ('TOPIC_LIST_PREPARE_START_FOR_TEACHER', + 'Подготовка списка тем для студентов', + E'Уважаемый $${ФИО_НАУЧ_РУК},\nпросим вас подготовить и отправить список тем для выбора студентами.\n\nС уважением, администрация.', + NOW(), + NOW()), + ('TOPIC_CHOOSE_FOR_STUDENT', + 'Выбор темы для работы', + E'Уважаемый $${ФИО_СТУД},\nПожалуйста, выберите тему вашей работы из предложенного списка до конца недели.\n\nС уважением, администрация.', + NOW(), + NOW()), + ('TOPIC_CHOOSE_FOR_TEACHER', + 'Утверждение выбора тем студентами', + E'Уважаемый $${ФИО_НАУЧ_РУК},\nСтуденты начали выбор тем.\nПросим вас утвердить.\n\nС уважением, администрация.', + NOW(), + NOW()), + ('TASK_CREATE_FOR_TEACHER', + 'Создание задания для студента', + E'Уважаемый $${ФИО_НАУЧ_РУК},\nПросим вас создать задание для студента $${ФИО_СТУД}.\n\nС уважением, администрация.', + NOW(), + NOW()), + ('TASK_ASSIGN_FOR_TEACHER', + 'Назначение задания студенту', + E'Уважаемый $${ФИО_НАУЧ_РУК},\nЗадание для студента $${ФИО_СТУД} назначено. Просим вас проконтролировать выполнение.\n\nС уважением, администрация.', + NOW(), + NOW()), + ('PRE_DIPLOMA_PRACTICE_START_FOR_TEACHER', + 'Начало преддипломной практики', + E'Уважаемый $${ФИО_НАУЧ_РУК},\nПреддипломная практика для студента $${ФИО_СТУД} начинается. Просим вас обеспечить руководство.\n\nС уважением, администрация.', + NOW(), + NOW()), + ('PRE_DIPLOMA_PRACTICE_START_FOR_STUDENT', + 'Начало преддипломной практики', + E'Уважаемый $${ФИО_СТУД}, ваша\nПреддипломная практика начинается. Свяжитесь с научным руководителем $${ФИО_НАУЧ_РУК} для получения инструкций.\n\nС уважением, администрация.', + NOW(), + NOW()), + ('TOPIC_FINALIZATION_FOR_TEACHER', + 'Окончательное утверждение темы', + E'Уважаемый $${ФИО_НАУЧ_РУК},\nПросим вас окончательно утвердить тему работы студента $${ФИО_СТУД} до указанного срока.\n\nС уважением, администрация.', + NOW(), + NOW()), + ('PRE_DEFENSE_FOR_TEACHER', + 'Подготовка к предзащите', + E'Уважаемый $${ФИО_НАУЧ_РУК},\nПредзащита работы студента $${ФИО_СТУД} назначена. Просим вас подготовить необходимые материалы.\n\nС уважением, администрация.', + NOW(), + NOW()), + ('PRE_DEFENSE_FOR_STUDENT', + 'Подготовка к предзащите', + E'Уважаемый $${ФИО_СТУД},\nВаша предзащита назначена. Подготовьте материалы и согласуйте с $${ФИО_НАУЧ_РУК}.\n\nС уважением, администрация.', + NOW(), + NOW()), + ('ANTIPLAGIARISM_FOR_STUDENT', + 'Проверка работы на антиплагиат', + E'Уважаемый $${ФИО_СТУД},\nЗагрузите вашу работу в систему антиплагиата до указанного срока. Обратитесь к $${ФИО_НАУЧ_РУК} при необходимости.\n\nС уважением, администрация.', + NOW(), + NOW()), + ('DEFENSE_PREPARATION_FOR_STUDENT', + 'Подготовка к защите', + E'Уважаемый $${ФИО_СТУД},\nЗащита вашей работы приближается. Подготовьте презентацию и согласуйте с $${ФИО_НАУЧ_РУК}.\n\nС уважением, администрация.', + NOW(), + NOW()), + ('DEFENSE_PREPARATION_FOR_TEACHER', + 'Подготовка к защите студента', + E'Уважаемый $${ФИО_НАУЧ_РУК},\nЗащита работы студента $${ФИО_СТУД} назначена. Просим вас проконтролировать подготовку и оставить отзыв по работе студента.\n\nС уважением, администрация.', + NOW(), + NOW()); \ No newline at end of file diff --git a/server/src/main/resources/db/migration/V00510__Insert_system_administrator.sql b/server/src/main/resources/db/migration/V00700__Insert_system_administrator.sql similarity index 100% rename from server/src/main/resources/db/migration/V00510__Insert_system_administrator.sql rename to server/src/main/resources/db/migration/V00700__Insert_system_administrator.sql diff --git a/server/src/main/resources/db/test-data/V00070__Create__defence_table.sql b/server/src/main/resources/db/test-data/V00070__Create__defence_table.sql deleted file mode 100644 index c0bb458..0000000 --- a/server/src/main/resources/db/test-data/V00070__Create__defence_table.sql +++ /dev/null @@ -1,12 +0,0 @@ -create table defense -( - id bigserial primary key, - defence_date timestamptz, - - created_at timestamptz not null, - updated_at timestamptz -); - --- COMMENTS -comment on table defense is 'Таблица для хранения данных о защитах'; -comment on column defense.defence_date is 'Дата защиты'; \ No newline at end of file diff --git a/server/src/main/resources/db/test-data/diploma_topic.sql b/server/src/main/resources/db/test-data/diploma_topic.sql deleted file mode 100644 index 0294670..0000000 --- a/server/src/main/resources/db/test-data/diploma_topic.sql +++ /dev/null @@ -1,40 +0,0 @@ -INSERT INTO diploma_topic (name) -VALUES ('Мобильное приложение для заказа автозапчастей на платформе ABCP'), - ('Подсистема уведомления пользователей для программного комплекса "РискПроф. Учебный центр"'), - ('Веб-приложение "Таск-менеджер"'), - ('Интернет-магазин электроники'), - ('Информационная система для студентов БГТУ в Telegram'), - ('Автоматизированная система тестирования сотрудников для определения их компетенций: модуль подготовки тестов и учета результатов'), - ('Инструментарий для разработки динамической экосистемы игрового пространства. Подсистема управления поведением внутриигровых агентов'), - ('Корпоративное веб-приложение по управлению задачами'), - ('Подсистемы визуализации и аналитики для онлайн-сервиса поддержки scrum-доски'), - ('Интерактивная система обучения приемам работы с криптовалютой'), - ('Автоматизированная система учета деятельности салона по продаже автомобилей'), - ('Обучающее мобильное android-приложение'), - ('Автоматизированная система учета зуботехнической CAD/CAM лаборатории'), - ('Автоматизированная система тестирования сотрудников для определения их компетенций: модуль регистрации и управления кабинетами'), - ('Система мониторинга успеваемости обучающихся и посещаемости занятий образовательного учреждения'), - ('Мобильное приложение для организации работы репетитора'), - ('Модуль "Редактор рисков" для программного комплекса управления профессиональными рисками "1С. ЕОС ПБ"'), - ('Онлайн-сервис по продаже товаров (на примере ООО «ЭПК-2»'), - ('Веб-сервис по обучению Microsoft Excel'), - ('Инструментарий для разработки динамической экосистемы игрового пространства. Подсистема генерации наполнения помещений'), - ('Подсистема интеграции с федеральной государственной информационной системой по охране труда для программного комплекса «РискПроф. Учебный центр»'), - ('Разработка приложения голосовой авторизации на основе нейросетевого подхода'), - ('Автоматизированная система учета деятельности спортивного клуба'), - ('Модуль "Специальная оценка условий труда" для программного комплекса управления профессиональными рисками "1С. ЕОС ПБ"'), - ('Интернет-магазин по продаже спортивной обуви'), - ('Приложение интерактивной исторической карты'), - ('Миграция системы учёта с 1С:УТ на 1С:КА'), - ('Программный комплекс для автоматизированного проведения опросов и тестирования пользователей'), - ('Автоматизированное тестирование программного комплекса «Федеральная государственная информационная система специальной оценки условий труда»'), - ('Модуль "Единая информационно-справочная подсистема" для федеральной государственной информационной системы по охране труда'), - ('Подсистема сопровождения обучения по охране труда для программного комплекса «РискПроф. Учебный центр»'), - ('Программное обеспечение системы обучения и тестирования в мессенджере Telegram'), - ('Онлайн-калькулятор индексов влияния в играх взвешенного голосования'), - ('Telegram-бот с функциями обработки изображений'), - ('Подсистема администрирования автоматизированной системы для проведения платежей и перевода денежных средств'), - ('Подсистема администрирования для программного комплекса распределения студентов по руководителям выпускных квалификационных работ'), - ('Библиотека контроля качества данных в базах и хранилищах данных для программного комплекса MetaControl'), - ('Инструментарий для разработки динамической экосистемы игрового пространства. Подсистема генерации карты помещений'), - ('Программная система решения транспортной задачи методом генетического алгоритма для торговой сети'); diff --git a/server/src/main/resources/db/test-data/group.sql b/server/src/main/resources/db/test-data/group.sql deleted file mode 100644 index 0b0c6a4..0000000 --- a/server/src/main/resources/db/test-data/group.sql +++ /dev/null @@ -1,3 +0,0 @@ -INSERT INTO "group" (name, curator_teacher_id, created_at, updated_at) -VALUES ('ИВТ-1', 40, now(), now()), - ('ИВТ-2', 40, now(), now()); diff --git a/server/src/main/resources/db/test-data/student.sql b/server/src/main/resources/db/test-data/student.sql deleted file mode 100644 index 840c618..0000000 --- a/server/src/main/resources/db/test-data/student.sql +++ /dev/null @@ -1,98 +0,0 @@ -do -$$ - begin - INSERT INTO studentData (form, - protection_order, - magistracy, - digital_format_present, - mark_comment, - mark_practice, - predefence_comment, - normal_control, - anti_plagiarism, - note, - record_book_returned, - work, - user_id, - diploma_topic_id, - mentor_user_id, - group_id, - created_at) - VALUES (true, 1100, 'ПРИ, ИВТ или другой вуз', true, 5, 5, 'ок', 'Подписано', 7862, 'Акт о внедрении', true, - 'нет', 1, 1, 40, 3, now()), - (true, 1110, 'Да, но не уверен, в БГТУ ли', true, 5, 5, 'ок', 'Подписано', 8196, - 'Заявка, Акт о внедрении', true, 'ООО "ЦИРОБЗ"', 2, 2, 40, 3, now()), - (true, 3500, 'Нет', true, 3, 3, - 'Критически низкий уровень. Допущен под ответственность руководителя ВКР', 'Подписано', 7141, 'Иниц', - true, 'нет', 3, 3, 41, 3, now()), - (true, 1800, 'Да, но не уверен, в БГТУ ли', true, 4, 5, 'Усилить работу', 'Подписано', 5381, 'Иниц', - true, 'нет', 4, 4, 42, 3, now()), - (true, 2100, 'ИВТ, ПРИ', true, 5, 5, 'ок', 'Подписано', 8146, 'Заявка, Акт о внедрении', true, 'нет', 5, - 5, 43, 3, now()), - (true, 1100, 'ИВТ, ПРИ', true, 5, 5, 'ок', 'Подписано', 7965, 'Иниц', true, 'нет', 6, 6, 41, 3, now()), - (true, 1200, 'нет', true, 5, 4, 'Усилить работу', 'Подписано', 8583, Null, true, - 'Газ Энерго Комплект (ГЭК)', 7, 7, 44, 3, now()), - (true, 1700, 'Нет', true, 5, 5, - 'Критически низкий уровень. Допущен под ответственность руководителя ВКР', 'Подписано', 6647, Null, - true, 'нет', 8, 8, 45, 3, now()), - (true, 3300, 'ИВТ, ПРИ или другой вуз', true, 5, 5, 'Усилить работу', 'Подписано', 6741, - 'Заявка, Акт о внедрении', true, 'нет', 9, 9, 46, 3, now()), - (true, 1200, 'ИВТ', true, 5, 5, 'ок', 'Подписано', 7645, 'Заявка, Акт о внедрении', true, 'нет', 10, 10, - 40, 3, now()), - (true, 3700, 'нет', true, 3, 3, - 'Критически низкий уровень. Допущен под ответственность руководителя ВКР', '1', 3093, 'Иниц', true, - 'нет', 11, 11, 45, 3, now()), - (true, 1900, 'Нет', true, 5, 5, 'ок', 'Подписано', 8175, 'Заявка, Акт о внедрении', true, 'нет', 12, 12, - 42, 3, now()), - (true, 3200, 'ИВТ', true, 5, 5, 'Усилить работу', 'Подписано', 7805, 'Акт', true, 'нет', 13, 13, 46, 3, - now()), - (true, 1200, 'ИВТ, ПРИ', true, 4, 4, 'ок', 'рек', 7590, 'Иниц', true, 'нет', 14, 14, 45, 3, now()), - (true, 3900, 'нет', true, 5, 5, 'Усилить работу', 'Подписано', 6463, 'Заявка, Акт о внедрении', true, - 'нет', 15, 15, 40, 3, now()), - (true, 2200, 'ИВТ', true, 5, 5, 'ок', 'Подписано', 7441, Null, true, 'ООО "ЦИРОБЗ"', 16, 16, 44, 3, - now()), - (true, 1130, 'Нет', true, 5, 4, - 'Критически низкий уровень. Допущен под ответственность руководителя ВКР', 'Подписано', 7319, - 'Заявка, Акт о внедрении', true, 'нет', 17, 17, 40, 3, now()), - (true, 2400, 'ИВТ', true, 4, 5, 'ок', 'Подписано', 6436, Null, true, 'нет', 18, 18, 45, 3, now()), - (true, 1600, 'ИВТ', true, 5, 5, 'Усилить работу', 'Подписано', 6227, 'Исслед', true, 'АО "БЭМЗ"', 19, 19, - 47, 3, now()), - (true, 1400, 'Нет', true, 5, 5, 'ок', 'Подписано', 8935, 'Иниц', true, 'ООО "ЦИРОБЗ"', 20, 20, 44, 4, - now()), - (true, 2900, 'ИВТ', true, 5, 5, 'ок', 'Подписано', 7971, 'Заявка, Акт о внедрении', true, 'нет', 21, 21, - 40, 4, now()), - (true, 2600, 'ИВТ, ПРИ', true, 3, 3, 'ок', 'Подписано', 7284, Null, true, 'нет', 22, 22, 48, 4, now()), - (true, 3100, 'ИВТ, ПРИ', true, 5, 5, 'Усилить работу', 'Подписано', 4966, 'Заявка, Акт о внедрении', - true, 'нет', 23, 23, 45, 4, now()), - (true, 3110, 'Нет', true, 5, 5, 'ок', 'Подписано', 7174, Null, true, 'нет', 24, 24, 40, 4, now()), - (true, 3800, 'Нет', true, 5, 5, 'ок', 'Подписано', 7233, Null, true, 'нет', 25, 25, 45, 4, now()), - (true, 3300, 'ИВТ', true, 5, 5, 'ок', 'Подписано', 7133, Null, true, 'нет', 26, 26, 43, 4, now()), - (true, 3130, 'ИВТ', true, 5, 5, 'ок', 'Подписано', 8083, 'Заявка, Акт о внедрении', true, 'нет', 27, 27, - 49, 4, now()), - (true, 3400, 'Нет', true, 5, 4, - 'Критически низкий уровень. Допущен под ответственность руководителя ВКР', 'Подписано', 7968, 'Иниц', - true, 'нет', 28, 28, 50, 4, now()), - (true, 3120, 'ИВТ или ПРИ', true, 5, 5, 'ок', 'Подписано', 7940, 'Исслед', true, 'ООО "ЦИРОБЗ"', 29, 29, - 40, 4, now()), - (true, 2800, 'Нет (возможно)', true, 4, 4, 'Усилить работу', 'рек', 6775, 'Заявка, Акт о внедрении', - true, 'нет', 30, 30, 40, 4, now()), - (true, 2100, 'Нет (возможно)', true, 5, 5, 'ок', 'Подписано', 7637, 'Заявка, Акт о внедрении', true, - 'нет', 31, 31, 40, 4, now()), - (true, 2700, 'ИВТ', true, 5, 5, 'ок', 'Подписано', 8544, 'Заявка, Акт о внедрении', true, 'нет', 32, 32, - 45, 4, now()), - (true, 2130, 'ИВТ, ПРИ', true, 5, 5, 'ок', 'Подписано', 7166, 'Заявка, Акт о внедрении', true, 'нет', 33, - 33, 51, 4, now()), - (true, 2110, 'ИВТ', true, 5, 5, 'ок', 'Подписано', 6075, 'Заявка, Акт о внедрении', true, 'нет', 34, 34, - 52, 4, now()), - (true, 3100, 'ИВТ', true, 5, 5, 'ок', 'Подписано', 7057, 'Заявка, Акт о внедрении', true, 'нет', 35, 35, - 50, 4, now()), - (true, 2120, 'В БГТУ на другой кафедре (38.04.01 Экономика или 27.04.05. Инноватика)', true, 5, 5, 'ок', - 'Подписано', 7057, 'Заявка, Акт о внедрении', true, 'нет', 36, 36, 51, 4, now()), - (true, 2500, 'ИВТ', true, 5, 5, 'ок', 'Подписано', 6583, 'Заявка, Акт о внедрении', true, 'нет', 37, 37, - 53, 4, now()), - (true, 1300, 'ИВТ, ПРИ', true, 5, 5, 'ок', 'Подписано', 8444, 'Заявка, Акт о внедрении', true, 'нет', 38, - 38, 44, 4, now()), - (true, 3600, 'ИВТ', true, 5, 5, 'ок', 'Подписано', 7631, 'Заявка, Акт о внедрении', true, 'нет', 39, 39, - 45, 4, now()); - end -$$; \ No newline at end of file diff --git a/server/src/main/resources/db/test-data/user.sql b/server/src/main/resources/db/test-data/user.sql deleted file mode 100644 index 7516226..0000000 --- a/server/src/main/resources/db/test-data/user.sql +++ /dev/null @@ -1,82 +0,0 @@ -INSERT INTO "user" (login, password, full_name, mail, number_phone, created_at) -VALUES ('akulenko_mikhail', '{noop}1', 'Акуленко Михаил Вячеславович', 'akulenko.mikhail@example.com', - '+79110000001', NOW()), - ('borovikov_artem', '{noop}1', 'Боровиков Артём Викторович', 'borovikov.artem@example.com', '+79110000002', - NOW()), - ('bykonya_alexey', '{noop}1', 'Быконя Алексей Николаевич', 'bykonya.alexey@example.com', '+79110000003', - NOW()), - ('ermakov_alexander', '{noop}1', 'Ермаков Александр Сергеевич', 'ermakov.alexander@example.com', - '+79110000004', NOW()), - ('zgursky_evgeny', '{noop}1', 'Згурский Евгений Олегович', 'zgursky.evgeny@example.com', '+79110000005', - NOW()), - ('ibishov_tural', '{noop}1', 'Ибишов Турал Садай оглы', 'ibishov.tural@example.com', '+79110000006', NOW()), - ('ignatenko_vladimir', '{noop}1', 'Игнатенко Владимир Алексеевич', 'ignatenko.vladimir@example.com', - '+79110000007', NOW()), - ('lazukin_danila', '{noop}1', 'Лазукин Данила Дмитриевич', 'lazukin.danila@example.com', '+79110000008', - NOW()), - ('mitiaev_danila', '{noop}1', 'Митяев Данила Алексеевич', 'mitiaev.danila@example.com', '+79110000009', - NOW()), - ('neshkov_daniil', '{noop}1', 'Нешков Даниил Владимирович', 'neshkov.daniil@example.com', '+79110000010', - NOW()), - ('petrov_pavel', '{noop}1', 'Петров Павел Сергеевич', 'petrov.pavel@example.com', '+79110000011', NOW()), - ('sazonov_andrey', '{noop}1', 'Сазонов Андрей Андреевич', 'sazonov.andrey@example.com', '+79110000012', - NOW()), - ('solokhin_maxim', '{noop}1', 'Солохин Максим Николаевич', 'solokhin.maxim@example.com', '+79110000013', - NOW()), - ('sochinsky_artem', '{noop}1', 'Сочинский Артем Александрович', 'sochinsky.artem@example.com', - '+79110000014', NOW()), - ('trisvyatsky_kirill', '{noop}1', 'Трисвятский Кирилл Андреевич', 'trisvyatsky.kirill@example.com', - '+79110000015', NOW()), - ('turov_alexander', '{noop}1', 'Туров Александр Сергеевич', 'turov.alexander@example.com', '+79110000016', - NOW()), - ('shevtsova_alexandra', '{noop}1', 'Шевцова Александра Валерьевна', 'shevtsova.alexandra@example.com', - '+79110000017', NOW()), - ('kibalyuk_artem', '{noop}1', 'Кибалюк Артем Сергеевич', 'kibalyuk.artem@example.com', '+79110000018', NOW()), - ('shulindin_artem', '{noop}1', 'Шулындин Артём Андреевич', 'shulindin.artem@example.com', '+79110000019', - NOW()), - ('belyaev_egor', '{noop}1', 'Беляев Егор Андреевич', 'belyaev.egor@example.com', '+79110000020', NOW()), - ('berezhnoy_igor', '{noop}1', 'Бережной Игорь Александрович', 'berezhnoy.igor@example.com', '+79110000021', - NOW()), - ('bogun_pavel', '{noop}1', 'Богун Павел Сергеевич', 'bogun.pavel@example.com', '+79110000022', NOW()), - ('vaseykin_nikita', '{noop}1', 'Васейкин Никита Павлович', 'vaseykin.nikita@example.com', '+79110000023', - NOW()), - ('gomonov_nikita', '{noop}1', 'Гомонов Никита Алексеевич', 'gomonov.nikita@example.com', '+79110000024', - NOW()), - ('druyan_oleg', '{noop}1', 'Друян Олег Викторович', 'druyan.oleg@example.com', '+79110000025', NOW()), - ('ivanov_kirill', '{noop}1', 'Иванов Кирилл Эдуардович', 'ivanov.kirill@example.com', '+79110000026', NOW()), - ('ivanova_veronika', '{noop}1', 'Иванова Вероника Евгеньевна', 'ivanova.veronika@example.com', - '+79110000027', NOW()), - ('izotov_ivan', '{noop}1', 'Изотов Иван Алексеевич', 'izotov.ivan@example.com', '+79110000028', NOW()), - ('isakov_zahar', '{noop}1', 'Исаков Захар Александрович', 'isakov.zahar@example.com', '+79110000029', NOW()), - ('iskritsky_daniil', '{noop}1', 'Искрицкий Даниил Павлович', 'iskritsky.daniil@example.com', '+79110000030', - NOW()), - ('linko_daria', '{noop}1', 'Линько Дарья Андреевна', 'linko.daria@example.com', '+79110000031', NOW()), - ('logutov_kirill', '{noop}1', 'Логутов Кирилл Александрович', 'logutov.kirill@example.com', '+79110000032', - NOW()), - ('nekrassov_sergey', '{noop}1', 'Некрасов Сергей Игоревич', 'nekrassov.sergey@example.com', '+79110000033', - NOW()), - ('sinyagin_ilya', '{noop}1', 'Синягин Илья Александрович', 'sinyagin.ilya@example.com', '+79110000034', - NOW()), - ('sopriko_daniil', '{noop}1', 'Соприко Даниил Сергеевич', 'sopriko.daniil@example.com', '+79110000035', - NOW()), - ('turovsky_ivan', '{noop}1', 'Туровский Иван Алексеевич', 'turovsky.ivan@example.com', '+79110000036', NOW()), - ('frantsev_sergey', '{noop}1', 'Францев Сергей Дмитриевич', 'frantsev.sergey@example.com', '+79110000037', - NOW()), - ('chepurnoy_maxim', '{noop}1', 'Чепурной Максим Романович', 'chepurnoy.maxim@example.com', '+79110000038', - NOW()), - ('schemelinin_dmitry', '{noop}1', 'Щемелинин Дмитрий Михайлович', 'schemelinin.dmitry@example.com', - '+79110000039', NOW()), - ('bulatitsky_d_i_1', '{noop}1', 'Булатицкий Д. И.', 'bulatitsky.d.i.1@example.com', '+79110000040', NOW()), - ('kopeliovich_d_i_1', '{noop}1', 'Копелиович Д. И.', 'kopeliovich.d.i.1@example.com', '+79110000041', NOW()), - ('dergachev_k_v', '{noop}1', 'Дергачев К. В.', 'dergachev.k.v@example.com', '+79110000042', NOW()), - ('trubakov_e_o', '{noop}1', 'Трубаков Е. О.', 'trubakov.e.o@example.com', '+79110000043', NOW()), - ('radchenko_a_o', '{noop}1', 'Радченко А. О.', 'radchenko.a.o@example.com', '+79110000044', NOW()), - ('zimin_s_n_1', '{noop}1', 'Зимин С. Н.', 'zimin.s.n.1@example.com', '+79110000045', NOW()), - ('koptenok_e_v', '{noop}1', 'Коптенок Е. В.', 'koptenok.e.v@example.com', '+79110000046', NOW()), - ('mikhaleva_o_a', '{noop}1', 'Михалева О. А.', 'mikhaleva.o.a@example.com', '+79110000047', NOW()), - ('gulakov_k_v', '{noop}1', 'Гулаков К. В.', 'gulakov.k.v@example.com', '+79110000048', NOW()), - ('titarev_d_v', '{noop}1', 'Титарёв Д. В.', 'titarev.d.v@example.com', '+79110000049', NOW()), - ('izrailev_v_ya_1', '{noop}1', 'Израилев В. Я.', 'izrailev.v.ya.1@example.com', '+79110000050', NOW()), - ('podvesovsky_a_g_1', '{noop}1', 'Подвесовский А. Г.', 'podvesovsky.a.g.1@example.com', '+79110000051', NOW()), - ('trubakov_a_o', '{noop}1', 'Трубаков А. О.', 'trubakov.a.o@example.com', '+79110000059', NOW()), - ('lageriev_d_g', '{noop}1', 'Лагерев Д. Г.', 'lageriev.d.g@example.com', '+79110000052', NOW()); diff --git a/server/src/main/resources/db/test-data/user_role.sql b/server/src/main/resources/db/test-data/user_role.sql deleted file mode 100644 index 4b00ae0..0000000 --- a/server/src/main/resources/db/test-data/user_role.sql +++ /dev/null @@ -1,58 +0,0 @@ -do -$$ - declare - teacher_id bigint := 1; - student_id bigint := 2; - commission_member_id bigint := 3; - administrator_id bigint := 4; - secretary_id bigint := 5; - begin - INSERT INTO user_role (user_id, role_id) - VALUES (1, student_id), - (2, student_id), - (3, student_id), - (4, student_id), - (5, student_id), - (6, student_id), - (7, student_id), - (8, student_id), - (9, student_id), - (10, student_id), - (11, student_id), - (12, student_id), - (13, student_id), - (14, student_id), - (15, student_id), - (16, student_id), - (17, student_id), - (18, student_id), - (19, student_id), - (20, student_id), - (21, student_id), - (22, student_id), - (23, student_id), - (24, student_id), - (25, student_id), - (26, student_id), - (27, student_id), - (28, student_id), - (29, student_id), - (30, student_id), - (31, student_id), - (32, student_id), - (33, student_id), - (34, student_id), - (35, student_id), - (36, student_id), - (37, teacher_id), - (37, administrator_id), - (38, commission_member_id), - (39, teacher_id), - (40, teacher_id), - (41, teacher_id), - (42, teacher_id), - (43, teacher_id), - (44, secretary_id), - (45, secretary_id); - end -$$; \ No newline at end of file diff --git a/web/package-lock.json b/web/package-lock.json index 346a708..9ebac0b 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -21,6 +21,7 @@ "mobx-react": "^9.1.1", "mobx-state-router": "^6.0.1", "react": "^18.2.0", + "react-beautiful-dnd": "^13.1.1", "react-bootstrap": "^2.10.4", "react-dom": "^18.2.0", "uuid": "^11.0.5" @@ -30,6 +31,7 @@ "@babel/preset-env": "^7.25.8", "@babel/preset-react": "^7.25.7", "@types/react": "^18.2.0", + "@types/react-beautiful-dnd": "^13.1.8", "@types/react-dom": "^18.2.0", "@types/webpack": "^5.28.5", "copy-webpack-plugin": "^13.0.0", @@ -2082,6 +2084,16 @@ "@types/send": "*" } }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.6.tgz", + "integrity": "sha512-lPByRJUer/iN/xa4qpyL0qmL11DqNW81iU/IG1S3uvRUq4oKagz8VCxZjiWkumgt66YT3vOdDgZ0o32sGKtCEw==", + "license": "MIT", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "node_modules/@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", @@ -2165,6 +2177,16 @@ "csstype": "^3.0.2" } }, + "node_modules/@types/react-beautiful-dnd": { + "version": "13.1.8", + "resolved": "https://registry.npmjs.org/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.1.8.tgz", + "integrity": "sha512-E3TyFsro9pQuK4r8S/OL6G99eq7p8v29sX0PM7oT8Z+PJfZvSQTx4zTQbUJ+QZXioAF0e7TGBEcA1XhYhCweyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/react-dom": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.1.tgz", @@ -2174,6 +2196,18 @@ "@types/react": "*" } }, + "node_modules/@types/react-redux": { + "version": "7.1.34", + "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.34.tgz", + "integrity": "sha512-GdFaVjEbYv4Fthm2ZLvj1VSCedV7TqE5y1kNwnjSdBOTXuRSgowux6J8TAct15T3CKBr63UMk+2CO7ilRhyrAQ==", + "license": "MIT", + "dependencies": { + "@types/hoist-non-react-statics": "^3.3.0", + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0", + "redux": "^4.0.0" + } + }, "node_modules/@types/react-transition-group": { "version": "4.4.11", "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.11.tgz", @@ -3256,6 +3290,15 @@ "node": ">= 8" } }, + "node_modules/css-box-model": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", + "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", + "license": "MIT", + "dependencies": { + "tiny-invariant": "^1.0.6" + } + }, "node_modules/css-loader": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", @@ -4130,6 +4173,15 @@ "value-equal": "^1.0.1" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, "node_modules/hpack.js": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", @@ -4746,6 +4798,12 @@ "url": "https://github.com/sponsors/streamich" } }, + "node_modules/memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", + "license": "MIT" + }, "node_modules/merge-descriptors": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", @@ -5412,6 +5470,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/raf-schd": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz", + "integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==", + "license": "MIT" + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -5465,6 +5529,26 @@ "node": ">=0.10.0" } }, + "node_modules/react-beautiful-dnd": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz", + "integrity": "sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ==", + "deprecated": "react-beautiful-dnd is now deprecated. Context and options: https://github.com/atlassian/react-beautiful-dnd/issues/2672", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.9.2", + "css-box-model": "^1.2.0", + "memoize-one": "^5.1.1", + "raf-schd": "^4.0.2", + "react-redux": "^7.2.0", + "redux": "^4.0.4", + "use-memo-one": "^1.1.1" + }, + "peerDependencies": { + "react": "^16.8.5 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.5 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-bootstrap": { "version": "2.10.5", "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.5.tgz", @@ -5516,6 +5600,37 @@ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" }, + "node_modules/react-redux": { + "version": "7.2.9", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz", + "integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.15.4", + "@types/react-redux": "^7.1.20", + "hoist-non-react-statics": "^3.3.2", + "loose-envify": "^1.4.0", + "prop-types": "^15.7.2", + "react-is": "^17.0.2" + }, + "peerDependencies": { + "react": "^16.8.3 || ^17 || ^18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/react-redux/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "license": "MIT" + }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -5569,6 +5684,15 @@ "node": ">= 10.13.0" } }, + "node_modules/redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -6569,6 +6693,15 @@ "punycode": "^2.1.0" } }, + "node_modules/use-memo-one": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz", + "integrity": "sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/use-sync-external-store": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz", diff --git a/web/package.json b/web/package.json index bc379ea..0d41ddc 100644 --- a/web/package.json +++ b/web/package.json @@ -20,6 +20,7 @@ "mobx-react": "^9.1.1", "mobx-state-router": "^6.0.1", "react": "^18.2.0", + "react-beautiful-dnd": "^13.1.1", "react-bootstrap": "^2.10.4", "react-dom": "^18.2.0", "uuid": "^11.0.5" @@ -29,6 +30,7 @@ "@babel/preset-env": "^7.25.8", "@babel/preset-react": "^7.25.7", "@types/react": "^18.2.0", + "@types/react-beautiful-dnd": "^13.1.8", "@types/react-dom": "^18.2.0", "@types/webpack": "^5.28.5", "copy-webpack-plugin": "^13.0.0", diff --git a/web/src/Application.tsx b/web/src/Application.tsx index 30bc287..e4d3bfd 100644 --- a/web/src/Application.tsx +++ b/web/src/Application.tsx @@ -12,15 +12,21 @@ import {ParticipantListPage} from "./components/participant/ParticipantListPage" import {DefenceListPage} from "./components/defence/DefenceListPage"; import {PreparationDirectionListPage} from "./components/dictionary/PreparationDirectionList"; import {DiplomaTopicListPage} from "./components/dictionary/DiplomaTopicList"; +import {DiplomaTopicAgreementPage} from "./components/tasks/DiplomaTopicAgreementPage"; +import {DefenceEditPage} from "./components/defence/DefenceEditPage"; +import {NotificationPage} from "./components/notification/NotificationPage"; const viewMap: ViewMap = { home: , + error: , participantList: , groupList: , defenceList: , themeList: , preparationDirectionList: , - error: , + defenceTopicAgreement: , + defenseEdit: , + notifications: , } const rootStore = initApp(); diff --git a/web/src/components/controls/ReactiveControls.tsx b/web/src/components/controls/ReactiveControls.tsx index f809c78..7db4871 100644 --- a/web/src/components/controls/ReactiveControls.tsx +++ b/web/src/components/controls/ReactiveControls.tsx @@ -9,7 +9,7 @@ import { observer } from "mobx-react"; import { - action, + action, computed, makeObservable, observable, runInAction @@ -41,6 +41,8 @@ import { import { ModalState } from "../../utils/modalState"; +import _ from "lodash"; +import {dateConverter, dateConverter2} from "../../utils/converters"; export interface ReactiveInputProps { value: ReactiveValue; @@ -112,7 +114,7 @@ export class StringInput extends Component> { return
+ value={this.props.value.value ?? ''} className={inputClassName}/>
@@ -279,16 +281,27 @@ export class DropdownSelectInput extends Component { if (this.props.value.value === undefined) { this.props.value.setAuto([]); } + this.initField(this.props); + runInAction(() => { this.options = props.possibleValues; }); } - componentDidUpdate(prevProps: Readonly) { - if (this.value != prevProps.value) { + componentDidUpdate() { + if (this.value != this.props.value) { this.initField(this.props); } + + if (!_.isEqual(this.options, this.props.possibleValues)) { + runInAction(() => { + this.options = this.props.possibleValues; + if (!this.props.disabled) { + this.value.setAuto([]); + } + }); + } } @action.bound @@ -341,24 +354,23 @@ export class DropdownSelectInput extends Component { const inputDisabledBackgroundStyle = this.props.disabled ? {backgroundColor: 'rgb(233, 236, 239)'} : {}; const inputDisabledColor = this.props.disabled ? '#000000' : 'rgb(33, 37, 41)'; - return
- { + return ( +
+ drop={'end'} style={inputDisabledBackgroundStyle}> @@ -386,11 +398,11 @@ export class DropdownSelectInput extends Component { } - } - -
+ +
+ ) } } @@ -449,3 +461,58 @@ export class TableInput extends Component> { editable={!this.disabled} editableModalState={this.searchNewEntryModalState}/> } } + +interface InputDateProps extends ReactiveInputProps { + maxDate?: Date; + minDate: Date; +} + +@observer +export class InputDate extends Component { + constructor(props: any) { + super(props); + makeObservable(this); + runInAction(() => { + this.myProps = props; + if (this.myProps.value.value === undefined) { + this.myProps.value.setAuto(new Date()); + } + this.myProps.value.setField(this.myProps.label); + }) + } + + componentDidUpdate() { + if (this.myProps.value.value !== this.props.value.value || this.myProps.disabled !== this.props.disabled) { + runInAction(() => { + this.myProps = this.props; + }); + } + } + + @observable myProps: InputDateProps; + + @action.bound + onChange(event: React.ChangeEvent) { + this.myProps.value.set(new Date(event.target.value)); + console.log('onChange', event.target.value); + console.log('onChange', this.myProps.value.value); + } + + @computed + get dateConvert() { + if (!this.myProps.value.value) return ''; + return this.myProps.value.value.toISOString().split('T')[0]; + } + + render() { + const inputClassName = `${this.myProps.validateless ? '' : this.myProps.value.invalid ? 'bg-danger' : this.myProps.value.touched ? 'bg-success' : ''} bg-opacity-10`; + + return
+ + + + +
+ } +} diff --git a/web/src/components/data-tables/DataTable.tsx b/web/src/components/data-tables/DataTable.tsx index d29f4e5..38b512a 100644 --- a/web/src/components/data-tables/DataTable.tsx +++ b/web/src/components/data-tables/DataTable.tsx @@ -1,7 +1,7 @@ import {ComponentContext} from "../../utils/ComponentContext"; import {TableDescriptor} from "../../utils/tables"; import {observer} from "mobx-react"; -import {action, computed, makeObservable, observable, runInAction} from "mobx"; +import {action, computed, makeObservable, observable, reaction, runInAction} from "mobx"; import {Button, ButtonGroup, FormSelect, FormText, Table} from "react-bootstrap"; import _ from "lodash"; import React, {ChangeEvent} from "react"; @@ -24,6 +24,15 @@ export class DataTable extends ComponentContext & { classNa constructor(props: DataTableProps) { super(props); makeObservable(this); + + reaction(() => this.descriptor.pageable, () => { + if (!this.descriptor.pageable) { + this.descriptor.pageSize = this.descriptor.data.length; + } else { + this.descriptor.page = 0; + this.descriptor.pageSize = _.toNumber(10); + } + }, {fireImmediately: true}); } @observable descriptor = this.props.tableDescriptor; @@ -35,19 +44,6 @@ export class DataTable extends ComponentContext & { classNa @observable className = this.props.className; @observable additionalButtons?: React.ReactNode = this.props.additionalButtons; - componentDidUpdate() { - runInAction(() => { - this.descriptor = this.props.tableDescriptor; - this.name = this.props.name; - this.headless = this.props.headless; - this.filterModalState = this.props.filterModalState; - this.editable = this.props.editable; - this.editableModalState = this.props.editableModalState; - this.className = this.props.className; - this.additionalButtons = this.props.additionalButtons; - }); - } - @computed get isFirstPage() { return this.descriptor.page === 0; @@ -90,7 +86,7 @@ export class DataTable extends ComponentContext & { classNa this.descriptor.pageSize = _.toNumber(e.target.value); } - // not computed, since we want to show initial data, when no sorts applied + @computed get filteredData() { const filters = this.descriptor.filters.filter(filter => filter); return this.descriptor.data.filter(row => ((filters && filters.length) > 0 ? filters.every(filter => filter(row)) : true)); @@ -193,9 +189,8 @@ export class DataTable extends ComponentContext & { classNa borderLeft: firstColumn ? 'none' : '1px solid var(--bs-table-border-color)', borderRight: lastColumn ? 'none' : '1px solid var(--bs-table-border-color)', }; - return -
runInAction(() => { const other = this.descriptor.columns .filter(c => c.key !== column.key) @@ -258,12 +253,10 @@ export class DataTable extends ComponentContext & { classNa borderRight: lastColumn ? 'none' : '1px solid var(--bs-table-border-color)', borderBottom: lastRow && !this.editable ? 'none' : '1px solid var(--bs-table-border-color)', } - return + return {column.format(rowAny[column.key], row)} { - suffixElement && - {suffixElement} + suffixElement && {suffixElement} } }) diff --git a/web/src/components/defence/DayAndOrderChangeModal.tsx b/web/src/components/defence/DayAndOrderChangeModal.tsx new file mode 100644 index 0000000..eafa224 --- /dev/null +++ b/web/src/components/defence/DayAndOrderChangeModal.tsx @@ -0,0 +1,294 @@ +import React from "react"; +import {ModalState} from "../../utils/modalState"; +import {observer} from "mobx-react"; +import {action, makeObservable, observable, runInAction} from "mobx"; +import {Button, Card, Col, Modal, ModalBody, ModalFooter, ModalHeader, ModalTitle, Row} from "react-bootstrap"; +import {DragDropContext, Draggable, Droppable, DropResult} from 'react-beautiful-dnd'; +import {Defense} from "../../models/defense"; +import {fullName} from "../../models/participant"; +import _ from "lodash"; +import {get, post} from "../../utils/request"; +import {StudentData} from "../../models/studentData"; +import {NotificationService} from "../../services/NotificationService"; + +export interface DayAndOrderChangeModalProps { + modalState: ModalState; + defense: Defense; +} + +const getAllStudByDefId = (id: number) => { + return get('student/by-defense-id', {id: id}); +} + +const getStudByStud = (students: StudentData[]): DGDStudent[] => { + return students.map(stud => { return { + day: stud.protDay, + id: _.toString(stud.id), + fullName: fullName(stud.participant), + curatorName: stud.curator ? fullName(stud.curator?.participant) : "Не назначен", + order: stud.protOrder, + topicName: stud.diplomaTopic ? stud.diplomaTopic.name : "Не выбрана", + } as DGDStudent}); +} + +const groupStudentsByDay = (students: DGDStudent[]): [DGDDay[], DGDStudent[]] => { + const daysMap: { [key: number]: DGDDay } = {}; + const unassigned: DGDStudent[] = []; + students.forEach(student => { + if (student.day !== undefined && Number.isInteger(student.day)) { + const day = student.day; + if (!daysMap[day]) { + daysMap[day] = { + students: [student], + title: `День ${day}`, + id: day.toString(), + }; + } else { + daysMap[day].students.push(student); + } + } else { + unassigned.push(student); + } + }); + + return [Object.values(daysMap), unassigned]; +}; + +interface DGDStudent { + id: string; + fullName: string; + curatorName: string; + topicName: string; + day: number; + order: number; +} + +interface DGDDay { + id: string, + title: string, + students: DGDStudent[], +} + +class Fields { + constructor(private props: DayAndOrderChangeModalProps) { + makeObservable(this); + getAllStudByDefId(props.defense.id).then((stud) => { + runInAction(() => { + this.modalState = props.modalState; + this.students = getStudByStud(stud); + let grouped = groupStudentsByDay(this.students); + this.days = grouped[0]; + this.unassigned = grouped[1]; + }); + }); + } + + @observable students: DGDStudent[] = []; + @observable unassigned: DGDStudent[] = []; + @observable days: DGDDay[] = []; + @observable modalState: ModalState; + + @action.bound + onDragEnd(result: DropResult): void { + const { source, destination } = result; + if (!destination || (source.droppableId === destination.droppableId && source.index === destination.index)) { + return; + } + runInAction(() => { + const sourceId = source.droppableId; + const destId = destination.droppableId; + const getList = (id: string) => { + return id === 'unassigned' + ? this.unassigned + : this.days.find(day => day.id === id)?.students || []; + }; + const sourceList = getList(sourceId); + const destList = sourceId === destId ? sourceList : getList(destId); + const [movedItem] = sourceList.splice(source.index, 1); + destList.splice(destination.index, 0, movedItem); + movedItem.day = destId === 'unassigned' ? undefined : parseInt(destId); + }); + } + + + @action.bound + addDay(): void { + runInAction(() => { + const maxId = this.days.length > 0 ? Math.max(...this.days.map(d => _.toNumber(d.id))) : 0; + const newDay: DGDDay = { + id: _.toString(maxId + 1), + title: `День ${this.days.length + 1}`, + students: [], + }; + this.days = [...this.days, newDay]; + }); + } + + @action.bound + removeDay(dayId: string): void { + runInAction(() => { + const day = this.days.find(d => d.id === dayId); + if (day) { + day.students.forEach(student => { + student.day = undefined; + student.order = undefined; + }); + this.unassigned = [...this.unassigned, ...day.students]; + this.days = this.days.filter(d => d.id !== dayId); + this.days.forEach((day: DGDDay, idx) => { + day.id = _.toString(idx + 1); + day.title = `День ${day.id}`; + }) + this.students = [ + ...this.days.flatMap(d => d.students), + ...this.unassigned, + ]; + } + }); + } + + @action.bound + save(): void { + let data = { + defenseId: this.props.defense.id, + days: this.days.map((d) => { return { + number: _.toNumber(d.id), + studs: d.students.map((student: DGDStudent, idx) => { + return {id: _.toNumber(student.id), order: idx} + }), + }}), + unassigned: this.unassigned.map((student: DGDStudent) => { + return {id: _.toNumber(student.id)} + }), + }; + + console.log(data); + post('defense/save-order', data).then(() => { + NotificationService.success("День и порядок защиты успешно обновлен"); + setTimeout(() => { + window.location.reload(); + }, 2000); + }); + } +} + +@observer +export class DayAndOrderChangeModal extends React.Component { + constructor(props: any) { + super(props); + makeObservable(this); + runInAction(() => { + this.fields = new Fields(props); + }); + } + + @observable fields: Fields; + + render() { + return <> + { this.fields.modalState && + + Формирование дня и порядка защиты + + + + {this.fields.days.map(day => ( + + + + {day.title} + + + + {(provided) => ( + + {day.students.map((student, index) => ( + + {(providedInner) => ( +
+ <> +
{student.fullName} ({index + 1})
+ { + !_.isEmpty(student.topicName) && +
Тема ВКР: {student.topicName}
+ } + { + !_.isEmpty(student.curatorName) && +
Куратор: {student.curatorName}
+ } + +
+ )} +
+ ))} + {provided.placeholder} +
+ )} +
+
+ + ))} + + + Нераспределённые студенты + + {(provided) => ( + + {this.fields.unassigned.map((student, index) => ( + + {(providedInner) => ( +
+ <> +
{student.fullName}
+ { + !_.isEmpty(student.topicName) && +
Тема ВКР: {student.topicName}
+ } + { + !_.isEmpty(student.curatorName) && +
Куратор: {student.curatorName}
+ } + +
+ )} +
+ ))} + {provided.placeholder} +
+ )} +
+
+ +
+
+
+ + + + + +
+ } + + } +} \ No newline at end of file diff --git a/web/src/components/defence/DefenceEditPage.tsx b/web/src/components/defence/DefenceEditPage.tsx new file mode 100644 index 0000000..156f469 --- /dev/null +++ b/web/src/components/defence/DefenceEditPage.tsx @@ -0,0 +1,498 @@ +import {observer} from "mobx-react"; +import {Page} from "../layout/Page"; +import {action, makeObservable, observable, reaction, runInAction} from "mobx"; +import React from "react"; +import {RouterService} from "../../services/RouterService"; +import {get, getFile, post} from "../../utils/request"; +import {Defense, mapStatusName} from "../../models/defense"; +import {Button, Col, Row, Tab, Tabs} from "react-bootstrap"; +import {SelectInputValue} from "../controls/ReactiveControls"; +import {ReactiveValue} from "../../utils/reactive/reactiveValue"; +import {CommissionMember} from "../../models/commissionMember"; +import {fullName, Participant} from "../../models/participant"; +import _ from "lodash"; +import {Group} from "../../models/group"; +import {dateConverter} from "../../utils/converters"; +import {NotificationService} from "../../services/NotificationService"; +import {UserService} from "../../services/UserService"; +import {Column, TableDescriptor} from "../../utils/tables"; +import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; +import {StudentData} from "../../models/studentData"; +import {ModalState} from "../../utils/modalState"; +import {DataTable} from "../data-tables/DataTable"; +import ParticipantProfileModal from "../participant/ParticipantProfileModal"; +import {DayAndOrderChangeModal} from "./DayAndOrderChangeModal"; + +interface GroupItemData { + groupsId: number; + studId: number; + groupName: string; + number: number; + fio: string; + pdpMark: number; + topic: string; + teacherFio: string; + dayOfProt: number; + placeOfProt: number; + magistracy: boolean; + electronic: boolean; + otziv: number; + dpedefenseMark: number; + normcontrol: boolean; + antipl: number; + vnedreniye: boolean; + otlichiye: boolean; + zachetka: boolean; + work: string; +} + +interface DefenseTableData { + defenseId: number; + groups: GroupItemData[]; +} + +const getDefenseTable = (defenseId: number) => { + return get('defense/defense-table', {defenseId: defenseId}); +} + +const getDefenseById = (defenseId: string) => { + return get('defense/get-by-id', {id: defenseId}); +} + +const mapGek = (g: CommissionMember) => { + return {value: _.toString(g.id), label: fullName(g.participant)} as SelectInputValue; +} + +const mapGroup = (g: Group) => { + return {value: _.toString(g.id), label: g.name} as SelectInputValue; +} + +const getStudentById = (studentId: number) => { + return get('student/by-id', {id: studentId}); +} + +class Fields { + constructor() { + makeObservable(this); + + reaction(() => { + return {td: this.defenseTableData, dtd: this.dayDefenseTableData} + }, () => { + if (!this.defenseTableData) return; + let descriptors: TableDescriptor[] = []; + this.defenseTableData.forEach((val) => { + descriptors.push( + new TableDescriptor([ + new Column('number', '№', (v) => v ? v : '-'), + new Column('fio', 'ФИО студента', (v) => v ? v : '-', (gid: GroupItemData) => { + return ; + }), + new Column('topic', 'Тема', (v) => v ? v : '-'), + new Column('pdpMark', 'Преддипломная практика', (v) => v ? v : '-'), + new Column('teacherFio', 'Руководитель', (v) => v ? v : '-'), + new Column('dayOfProt', 'День защиты', (v) => v ? v : '-'), + new Column('placeOfProt', 'Порядок защиты', (v) => v ? v : '-'), + new Column('magistracy', 'Магистратура', (v) => v ? '+' : '-'), + new Column('electronic', 'Работа в эл. виде', (v) => v ? '+' : '-', undefined, undefined, "5"), + new Column('otziv', 'Отзыв', (v) => v ? v : '-'), + new Column('dpedefenseMark', 'Предзащита', (v) => v ? v : '-'), + new Column('normcontrol', 'Нормоконтроль', (v) => v ? '+' : '-'), + new Column('antipl', 'Антиплагиат', (v) => v ? `${v}%` : '-'), + new Column('vnedreniye', 'Примечания', (v, data) => { + let string = ""; + if (data.vnedreniye) string += "Заявка и акт о внедрении"; + if (data.otlichiye) { + if (string.length > 0) string += ",\n"; + string += "Заявка на диплом с отличием"; + } + return string; + }), + new Column('zachetka', 'Зачетка сдана', (v) => v ? '+' : '-'), + new Column('work', 'Работа', (v) => v ? v : '-'), + ], val, false + ) + ); + }); + this.tableDescriptors = descriptors; + + let dayDescriptors: TableDescriptor[] = []; + this.dayDefenseTableData.forEach((val) => { + dayDescriptors.push( + new TableDescriptor([ + new Column('number', '№', (v) => v ? v : '-'), + new Column('fio', 'ФИО студента', (v) => v ? v : '-', (gid: GroupItemData) => { + return ; + }), + new Column('topic', 'Тема', (v) => v ? v : '-'), + new Column('pdpMark', 'Преддипломная практика', (v) => v ? v : '-'), + new Column('teacherFio', 'Руководитель', (v) => v ? v : '-'), + new Column('dayOfProt', 'День защиты', (v) => v ? v : '-'), + new Column('placeOfProt', 'Порядок защиты', (v) => v ? v : '-'), + new Column('magistracy', 'Магистратура', (v) => v ? '+' : '-'), + new Column('electronic', 'Работа в эл. виде', (v) => v ? '+' : '-', undefined, undefined, "5"), + new Column('otziv', 'Отзыв', (v) => v ? v : '-'), + new Column('dpedefenseMark', 'Предзащита', (v) => v ? v : '-'), + new Column('normcontrol', 'Нормоконтроль', (v) => v ? '+' : '-'), + new Column('antipl', 'Антиплагиат', (v) => v ? `${v}%` : '-'), + new Column('vnedreniye', 'Примечания', (v, data) => { + let string = ""; + if (data.vnedreniye) string += "Заявка и акт о внедрении"; + if (data.otlichiye) { + if (string.length > 0) string += ",\n"; + string += "Заявка на диплом с отличием"; + } + return string; + }), + new Column('zachetka', 'Зачетка сдана', (v) => v ? '+' : '-'), + new Column('work', 'Работа', (v) => v ? v : '-'), + ], val, false + ) + ); + }); + this.dayTableDescriptors = dayDescriptors; + }, {fireImmediately: true}); + + reaction(() => { + return {user: UserService.user, def: this.defense} + }, () => { + this.canChangeDefense = UserService.isSecretary + || UserService.isAdministrator + || UserService.isDirOfPrepResponsible(this.defense?.preparationDirection); + }, {fireImmediately: true}); + + let routerOptions = RouterService.getOptions(); + + if (!routerOptions.defenseId) { + NotificationService.error('Не наедена защита'); + } else { + getDefenseById(routerOptions.defenseId).then((defense: Defense) => { + runInAction(() => { + this.defense = defense; + if (defense.commissionMembers && !_.isEmpty(defense.commissionMembers)) { + this.allGek = defense.commissionMembers.map(mapGek); + this.selectedGek.setAuto(this.allGek); + } + if (defense.groups && !_.isEmpty(defense.groups)) { + this.allGroups = defense.groups.map(mapGroup); + this.selectedGroups.setAuto(this.allGroups); + } + this.defDate.setAuto(dateConverter(defense.defenseDate)); + this.status.setAuto(mapStatusName(defense.status)); + + this.updateDefData(); + }) + }); + } + } + + @observable defense: Defense; + + @observable allGek: SelectInputValue[]; + @observable selectedGek = new ReactiveValue(); + + @observable allGroups: SelectInputValue[] = []; + @observable selectedGroups = new ReactiveValue(); + + @observable defDate = new ReactiveValue(); + + @observable status = new ReactiveValue(); + + @observable canChangeDefense: boolean; + + @observable defenseTableData: Map; + @observable tableDescriptors: TableDescriptor[] = []; + @observable dayDefenseTableData: Map; + @observable dayTableDescriptors: TableDescriptor[] = []; + + @observable selectedPartic: Participant; + @observable participantModalState = new ModalState(false, this.updateDefData); + + @observable changeDefDayModalState = new ModalState(false, this.updateDefData); + + @action.bound + updateDefData() { + getDefenseTable(_.toNumber(this.defense.id)).then(dt => { + runInAction(() => { + let defTable = _.cloneDeep(dt); + const grouped = defTable.groups?.reduce((acc: Map, item: GroupItemData) => { + const group = acc.get(item.groupName) || []; + group.push(item); + acc.set(item.groupName, group); + return acc; + }, new Map()); + let tableData = new Map( + Array.from(grouped.entries()) + .sort((a, b) => a[0].localeCompare(b[0])) + .map(([key, items]) => [ + key, + items.sort((a, b) => a.fio.localeCompare(b.fio)), + ]) + ); + tableData.forEach((item: GroupItemData[]) => { + let number = 1; + item.forEach((item: GroupItemData) => { + item.number = number++; + }); + }); + this.defenseTableData = tableData; + + let dayDefTable = _.cloneDeep(dt); + const dayGrouped = dayDefTable.groups?.reduce((acc: Map, item: GroupItemData) => { + const defDay = acc.get(_.toString(item.dayOfProt)) || []; + defDay.push(item); + acc.set(_.toString(item.dayOfProt), defDay); + return acc; + }, new Map()); + let dayTableData = new Map( + Array.from(dayGrouped.entries()) + .sort((a, b) => _.toNumber(a[0]) - _.toNumber(b[0])) + .map(([key, items]) => [ + key, + items.filter(v => v.dayOfProt && v.placeOfProt) + .sort((a, b) => a.placeOfProt - b.placeOfProt), + ]) + ); + dayTableData.forEach((item: GroupItemData[]) => { + let number = 1; + item.forEach((item: GroupItemData) => { + item.number = number++; + }); + }); + this.dayDefenseTableData = dayTableData; + }); + }); + } + + @action.bound + openChangeDefDayModal() { + this.changeDefDayModalState.open(); + } + + @action.bound + requestToNextState() { + post(`defense/to-next-state`, {id: this.defense.id}).then(() => { + NotificationService.success("Защита успешно переведена на следующий этап"); + setTimeout(() => { + window.location.reload(); + }, 2000); + }); + } + + @action.bound + requestRecreateDefDay() { + post(`defense/recreate-protection-order`, {id: this.defense.id}).then(() => { + NotificationService.success("День и порядок защиты успешно сформированы"); + setTimeout(() => { + window.location.reload(); + }, 2000); + }); + } + + @action.bound + onIconClicked(event: React.MouseEvent) { + let studId = _.toNumber(event.currentTarget.getAttribute('data-value')); + if (studId) { + getStudentById(studId).then(student => { + runInAction(() => { + this.selectedPartic = student.participant; + this.participantModalState.open(); + }); + }); + } + } + + @action.bound + downloadList() { + getFile('doc/stud-list', {id: this.defense.id}, "лист.docx").then(); + } + + @action.bound + downloadBlank() { + getFile('doc/gek-questions', {id: this.defense.id}, "123.docx").then(); + } + + @action.bound + downloadFragment() { + getFile('doc/topic-list', {id: this.defense.id}, "123.docx").then(); + } +} + +@observer +export class DefenceEditPage extends Page { + constructor(props: any) { + super(props); + makeObservable(this); + runInAction(() => { + this.fields = new Fields(); + }); + } + + @observable fields: Fields; + + get page() { + return <> + + + } +} + +@observer +class MainData extends React.Component<{ fields: Fields }> { + constructor(props: any) { + super(props); + makeObservable(this); + runInAction(() => { + this.fields = props.fields; + }) + } + + @observable fields: Fields; + + render() { + return <> +

Защита

+ + + + + + + + + + + + + { + this.fields.canChangeDefense && +
+ +
+ } +
+ + { + this.fields.selectedPartic && + + } + + + { + this.fields.tableDescriptors.map(td => + td.data.length > 0 + ? }/> + : <> + ) + } + + { + this.fields.dayTableDescriptors && !_.isEmpty(this.fields.dayTableDescriptors) && + Array.from(this.fields.dayDefenseTableData.entries()).map(([k, v], i) => { + return ( + !_.isEmpty(v) + ? + + + : null + ) + }) + } + + { + this.fields.canChangeDefense && +
+ + +
+ } + { + this.fields.defense && + + } +
+ { + this.fields.canChangeDefense && + + + + + + } +
+ ; + } +} + +const Groups = observer(({fields}: { fields: Fields }) => { + return ( +
+
Защищающиеся группы:
+ { + fields.selectedGroups.value && +
+ { + fields.selectedGroups.value.map(g => <>{g.label}).reduce((prev, curr) => { + return <>{prev}
{curr} + }) + } +
+ } +
+ ); +}); + +const Gek = observer(({fields}: { fields: Fields }) => { + return ( +
+
Члены ГЭК:
+ { + fields.selectedGek.value && +
+ { + fields.selectedGek.value.map(g => <>{g.label}).reduce((prev, curr) => { + return <>{prev}
{curr} + }) + } +
+ } +
+ ); +}); + +const DefDate = observer(({fields}: { fields: Fields }) => { + return ( +
Дата начала защиты: {fields.defDate.value}
+ ); +}); + +const DefStatus = observer(({fields}: { fields: Fields }) => { + return ( +
Этап защиты: {fields.status.value}
+ ); +}); + +const AdditionalButtonsTable = observer(({fields}: { fields: Fields }) => { + return ( +
+
+ +
+
+ ); +}); diff --git a/web/src/components/defence/DefenceListPage.tsx b/web/src/components/defence/DefenceListPage.tsx index 3a0a0a6..23bf908 100644 --- a/web/src/components/defence/DefenceListPage.tsx +++ b/web/src/components/defence/DefenceListPage.tsx @@ -7,39 +7,54 @@ import React from "react"; import {ThinkService} from "../../services/ThinkService"; import {DataTable} from "../data-tables/DataTable"; import {Column, TableDescriptor} from "../../utils/tables"; -import {Defence} from "../../models/defence"; +import {Defense} from "../../models/defense"; import {Participant} from "../../models/participant"; import {datetimeConverter} from "../../utils/converters"; -import {StudentData} from "../../models/studentData"; +import {Button} from "react-bootstrap"; +import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; +import {DefenseInfoModal} from "./DefenseInfoModal"; +import {ModalState} from "../../utils/modalState"; +import _ from "lodash"; class DefenceListPageState { constructor() { makeObservable(this); this.updateDefences(); reaction(() => this.defences, () => { - this.tableDescriptor = new TableDescriptor([ - new Column('commissionMembers', 'ГЭК', p => p ? p.length : 0), - new Column('groups', 'Группы', g => g && g.length ? g.map(g => g.name).join(', ') : 'Пусто'), - new Column('groups', 'Студентов', g => { - if (!g) return 'Пусто'; - const students: StudentData[] = []; - g.forEach(group => group.students.forEach(student => students.push(student))); - return students.length; + this.tableDescriptor = new TableDescriptor([ + new Column('id', 'Идентификатор', (v) => v, (defense: Defense) => { + return ; }), - new Column('createdAt', 'Дата создания', datetimeConverter), - new Column('updatedAt', 'Дата обновления', datetimeConverter), + new Column('commissionMembers', 'ГЭК', p => p ? p.length : 'Пусто'), + new Column('groups', 'Группы', g => g && g.length ? g.map(g => g.name).join(', ') : 'Пусто'), + new Column('groups', 'Студентов', g => { + if (!g) return 'Пусто'; + let studLen = 0; + g.forEach(group => { + studLen += group.students.length; + }); + return studLen ? studLen : 'Пусто'; + }), + new Column('createdAt', 'Дата создания', datetimeConverter), + new Column('updatedAt', 'Дата обновления', datetimeConverter), ], this.defences, true ); }); } - @observable defences: Defence[] = []; - @observable tableDescriptor: TableDescriptor; + @observable defences: Defense[] = []; + @observable tableDescriptor: TableDescriptor; + + @observable selectedDefense: Defense = undefined; + + @observable editDefenseModalState = new ModalState(); + @observable newDefenseModalState = new ModalState(); @action.bound updateDefences() { ThinkService.think(); - get('/defence/all').then((defences) => { + get('/defense/get-all').then((defences) => { runInAction(() => { this.defences = defences; }); @@ -47,6 +62,15 @@ class DefenceListPageState { ThinkService.completeAll(); }); } + + @action.bound + onIconClicked(event: React.MouseEvent) { + const d = this.defences.find(d => d.id === _.toNumber(event.currentTarget.getAttribute('data-value'))); + if (d) { + this.selectedDefense = d; + this.editDefenseModalState.open(); + } + } } @observer @@ -62,13 +86,27 @@ export class DefenceListPage extends Page { get page() { return <> { - <> - { - this.fields.tableDescriptor && - - } - + this.fields.tableDescriptor && + }/> } + { + this.fields.selectedDefense && + + } + } } + +const AdditionalButtons = observer(({state}: { state: DefenceListPageState }) => { + return ( +
+
+ +
+
+ ); +}); \ No newline at end of file diff --git a/web/src/components/defence/DefenseInfoModal.tsx b/web/src/components/defence/DefenseInfoModal.tsx new file mode 100644 index 0000000..f39c4c3 --- /dev/null +++ b/web/src/components/defence/DefenseInfoModal.tsx @@ -0,0 +1,291 @@ +import React, {Component} from "react"; +import {ModalState} from "../../utils/modalState"; +import {Defense, mapStatusName} from "../../models/defense"; +import {observer} from "mobx-react"; +import {action, computed, makeObservable, observable, reaction, runInAction} from "mobx"; +import {Button, Modal} from "react-bootstrap"; +import {DropdownSelectInput, InputDate, SelectInputValue, StringInput} from "../controls/ReactiveControls"; +import {ReactiveValue} from "../../utils/reactive/reactiveValue"; +import {required} from "../../utils/reactive/validators"; +import _ from "lodash"; +import {datetimeConverter} from "../../utils/converters"; +import {Group} from "../../models/group"; +import {fullName, Participant} from "../../models/participant"; +import {UserService} from "../../services/UserService"; +import {RouterService} from "../../services/RouterService"; +import {CommissionMember} from "../../models/commissionMember"; +import {get, post} from "../../utils/request"; +import {NotificationService} from "../../services/NotificationService"; +import {ThinkService} from "../../services/ThinkService"; +import {PreparationDirection} from "../../models/preparationDirection"; + +const mapGroup = (g: Group) => { + return {value: _.toString(g.id), label: g.name} as SelectInputValue; +} + +const mapGek = (g: CommissionMember) => { + return {value: _.toString(g.id), label: fullName(g.participant)} as SelectInputValue; +} + +const getAllGroups = () => { + return get("group/get-all-groups"); +} + +const getAllGek = () => { + return get("commission-member/get-all"); +} + +const getAllNorm = () => { + return get("participant/get-all-norm"); +} + +const getAllAntipl = () => { + return get("participant/get-all-antipl"); +} + +const mapPartic = (p: Participant) => { + return {value: _.toString(p.id), label: fullName(p)} as SelectInputValue; +} + +const getAllDirOrPrep = () => { + return get('prep-direction/get-all'); +} + +const mapDirOfPrep = (dp: PreparationDirection) => { + return { value: _.toString(dp.id), label: dp.code } as SelectInputValue; +} + +class Fields { + constructor(state: ModalState, defense?: Defense) { + makeObservable(this); + runInAction(() => { + this.modalState = state; + + getAllGroups().then(groups => { + runInAction(() => { + this.allGroups = groups; + }); + }); + + getAllGek().then(geks => { + runInAction(() => { + this.allGek = geks.map(mapGek); + }); + }); + + getAllNorm().then(norms => { + runInAction(() => { + this.allNorm = norms.map(mapPartic); + }); + }); + + getAllAntipl().then(norms => { + runInAction(() => { + this.allAntipl = norms.map(mapPartic); + }) + }); + + getAllDirOrPrep().then(dp => { + runInAction(() => { + this.allDirOfPrep = dp.map(mapDirOfPrep); + }) + }); + + reaction(() => this.dirOfPrep.value, () => { + if (this.dirOfPrep.value && this.dirOfPrep.value[0]) { + this.filteredGroups = this.allGroups + .filter(g => g.preparationDirection.id === _.toNumber(this.dirOfPrep.value[0].value)) + .map(mapGroup); + } else { + this.filteredGroups = []; + } + }, {fireImmediately: true}); + + if (defense) { + this.editMode = false; + this.alreadyExists = true; + this.modalState = state; + this.defense = defense; + this.createdAt.setAuto(datetimeConverter(this.defense.createdAt)); + this.updatedAt.setAuto(datetimeConverter(this.defense.updatedAt)); + this.groups.setAuto(this.defense.groups?.map(mapGroup)); + this.gek.setAuto(this.defense.commissionMembers?.map(mapGek)); + if (defense.responsibleForNorm) this.selectedNorm.setAuto([mapPartic(this.defense.responsibleForNorm)]); + if (defense.responsibleForAntipl) this.selectedAntipl.setAuto([mapPartic(this.defense.responsibleForAntipl)]); + if (defense.preparationDirection) this.dirOfPrep.setAuto([mapDirOfPrep(this.defense.preparationDirection)]); + this.status.setAuto(mapStatusName(this.defense.status)); + if (defense.defenseDate) this.startDate.setAuto(new Date(defense.defenseDate)); + } else { + this.editMode = true; + this.status.setAuto(mapStatusName("NOT_STARTED")); + } + }); + } + + @observable editMode: boolean; + @observable alreadyExists = false; + + @observable modalState: ModalState; + @observable defense: Defense; + + @observable allGroups: Group[] = []; + @observable filteredGroups: SelectInputValue[] = []; + @observable groups = new ReactiveValue().addValidator(required); + + @observable allDirOfPrep: SelectInputValue[] = []; + @observable dirOfPrep = new ReactiveValue().addValidator(required); + + @observable allGek: SelectInputValue[] = []; + @observable gek = new ReactiveValue().addValidator(required); + + @observable allAntipl: SelectInputValue[] = []; + @observable selectedAntipl = new ReactiveValue().addValidator(required); + + @observable allNorm: SelectInputValue[] = []; + @observable selectedNorm = new ReactiveValue().addValidator(required); + + @observable status = new ReactiveValue().addValidator(required).setAuto(""); + + @observable createdAt = new ReactiveValue().setAuto(""); + @observable updatedAt = new ReactiveValue().setAuto(""); + + @observable startDate = new ReactiveValue().addValidator(required); + + isAnyInvalid() { + let invalid = this.gek.invalid || this.groups.invalid || this.selectedAntipl.invalid || this.selectedNorm.invalid || this.dirOfPrep.invalid; + + if (!this.alreadyExists) { + invalid = invalid || !this.gek.touched || !this.groups.touched || !this.selectedAntipl.touched || !this.selectedNorm.touched || !this.dirOfPrep.touched; + } + + return invalid; + } + + touchAll() { + this.gek.touch(); + this.groups.touch(); + this.selectedAntipl.touch(); + this.selectedNorm.touch(); + } + + @action.bound + save() { + if (this.isAnyInvalid()) { + this.touchAll(); + NotificationService.error("Исправьте ошибки на форме"); + return; + } + + const data: Defense = { + id: this.defense ? this.defense.id : undefined, + groups: this.groups.value.map(g => { + return {id: _.toNumber(g.value)} as Group + }), + commissionMembers: this.gek.value.map(g => { + return {id: _.toNumber(g.value)} as CommissionMember + }), + responsibleForAntipl: this.selectedAntipl.value && this.selectedAntipl.value[0] ? { id: _.toNumber(this.selectedAntipl.value[0].value) } : undefined, + responsibleForNorm: this.selectedNorm.value && this.selectedNorm.value[0] ? { id: _.toNumber(this.selectedNorm.value[0].value) } : undefined, + preparationDirection: this.dirOfPrep.value && this.dirOfPrep.value[0] ? { id: _.toNumber(this.dirOfPrep.value[0].value) } : undefined, + defenseDate: this.startDate.value ? this.startDate.value.toISOString() : undefined, + }; + + ThinkService.think(); + post("defense/save", data).then(() => { + if (this.defense) + NotificationService.success("Защита успешно сохранена"); + else + NotificationService.success("Защита успешно изменена"); + }).finally(() => { + ThinkService.completeAll(); + }); + + this.editMode = false; + this.modalState.close(); + } +} + +@observer +export class DefenseInfoModal extends Component<{ modalState: ModalState, defense?: Defense }> { + constructor(props: any) { + super(props); + makeObservable(this); + runInAction(() => { + this.fields = new Fields(this.props.modalState, this.props.defense); + }); + } + + componentDidUpdate() { + if (this.fields.defense != this.props.defense) { + runInAction(() => { + this.fields = new Fields(this.props.modalState, this.props.defense); + }); + } + } + + @observable fields: Fields; + + render() { + + + + return ( + + + + {this.fields.editMode + ? "Редактирование защиты" + : "Защита"} + + + + + + + + + + { + let date = new Date(); + date.setFullYear(date.getFullYear() + 2); + return date; + })()} minDate={new Date()} value={this.fields.startDate} disabled={!this.fields.editMode}/> + + + + + { + !this.fields.editMode && + + } + { + (UserService.isAdministrator || UserService.isSecretary || UserService.isDirOfPrepResponsible(this.fields.defense?.preparationDirection)) && !this.fields.editMode && + + } + { + (UserService.isAdministrator || UserService.isSecretary || UserService.isDirOfPrepResponsible(this.fields.defense?.preparationDirection)) && this.fields.editMode && + + } + + + + ) + } +} \ No newline at end of file diff --git a/web/src/components/dictionary/DiplomaTopicFilterModal.tsx b/web/src/components/dictionary/DiplomaTopicFilterModal.tsx new file mode 100644 index 0000000..b9b3672 --- /dev/null +++ b/web/src/components/dictionary/DiplomaTopicFilterModal.tsx @@ -0,0 +1,85 @@ +import {ModalState} from "../../utils/modalState"; +import {Group} from "../../models/group"; +import {observer} from "mobx-react"; +import React, {Component} from "react"; +import {action, makeObservable, observable, runInAction} from "mobx"; +import {ReactiveValue} from "../../utils/reactive/reactiveValue"; +import {Button, Modal, ModalBody, ModalFooter, ModalHeader, ModalTitle} from "react-bootstrap"; +import {StringInput} from "../controls/ReactiveControls"; +import {DiplomaTopic} from "../../models/diplomaTopic"; +import {fullName} from "../../models/participant"; + +export interface DiplomaTopicFilterProps { + modalState: ModalState; + filters: ((group: DiplomaTopic) => boolean)[]; +} + +@observer +export class DiplomaTopicFilterModal extends Component { + constructor(props: DiplomaTopicFilterProps) { + super(props); + makeObservable(this); + + runInAction(() => { + this.filters.push(this.nameFilter); + this.filters.push(this.pdCodeFilter); + this.filters.push(this.pdNameFilter); + this.filters.push(this.teacherNameFilter); + }); + } + + @observable filters = this.props.filters; + @observable modalState = this.props.modalState; + + @observable nameField = new ReactiveValue().syncWithParam('name'); + @observable prepDirNameField = new ReactiveValue().syncWithParam('pdn'); + @observable prepDirCodeField = new ReactiveValue().syncWithParam('pdc'); + @observable teacherNameField = new ReactiveValue().syncWithParam('tn'); + + @observable nameFilter = (topic: DiplomaTopic) => { + if (!this.nameField.value) return true; + return topic.name?.includes(this.nameField.value) + }; + + @observable pdNameFilter = (topic: DiplomaTopic) => { + if (!this.prepDirNameField.value) return true; + return topic.preparationDirection.name?.includes(this.prepDirNameField.value) + } + + @observable pdCodeFilter = (topic: DiplomaTopic) => { + if (!this.prepDirCodeField.value) return true; + return topic.preparationDirection.code?.includes(this.prepDirCodeField.value) + } + + @observable teacherNameFilter = (topic: DiplomaTopic) => { + if (!this.teacherNameField.value) return true; + if (!topic.teacher || !topic.teacher.participant) return false; + return fullName(topic.teacher.participant).includes(this.teacherNameField.value); + } + + @action.bound + reset() { + this.nameField.set(""); + this.prepDirNameField.set(""); + this.prepDirCodeField.set(""); + this.teacherNameField.set(""); + } + + render() { + return + + Фильтр + + + + + + + + + + + + + } +} diff --git a/web/src/components/dictionary/DiplomaTopicList.tsx b/web/src/components/dictionary/DiplomaTopicList.tsx index 3703d08..59efb31 100644 --- a/web/src/components/dictionary/DiplomaTopicList.tsx +++ b/web/src/components/dictionary/DiplomaTopicList.tsx @@ -7,7 +7,7 @@ import {Column, TableDescriptor} from "../../utils/tables"; import {PreparationDirection} from "../../models/preparationDirection"; import {get} from "../../utils/request"; import {datetimeConverter} from "../../utils/converters"; -import {Button} from "react-bootstrap"; +import {Button, Col, Row} from "react-bootstrap"; import {ModalState} from "../../utils/modalState"; import {UserService} from "../../services/UserService"; import {DiplomaTopic} from "../../models/diplomaTopic"; @@ -16,6 +16,8 @@ import {fullName} from "../../models/participant"; import DiplomaTopicModal from "./DiplomaTopicModal"; import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; import _ from "lodash"; +import {ThinkService} from "../../services/ThinkService"; +import {DiplomaTopicFilterModal} from "./DiplomaTopicFilterModal"; const getAllDiplomaTopics = () => { return get("diploma-topic/all"); @@ -28,7 +30,7 @@ class DiplomaTopicPageState { reaction(() => this.diplomaTopics, () => { this.tableDescriptor = new TableDescriptor([ new Column('name', 'Название', undefined, (dp) => { - return ; }), new Column('teacher', 'Преподаватель', (value) => { @@ -42,7 +44,7 @@ class DiplomaTopicPageState { }), new Column('createdAt', 'Дата создания', datetimeConverter), new Column('updatedAt', 'Дата обновления', datetimeConverter), - ], this.diplomaTopics, true + ], this.diplomaTopics, true, this.filters ); }, {fireImmediately: true}); } @@ -53,12 +55,19 @@ class DiplomaTopicPageState { @observable diplomaTopicModalState = new ModalState(false, this.updateDiplomaTopics); @observable diplomaTopicEditModalState = new ModalState(false, this.updateDiplomaTopics); + @observable filters: ((group: DiplomaTopic) => boolean)[] = []; + + @observable filterModalState = new ModalState(); + @action.bound updateDiplomaTopics() { + ThinkService.think(); getAllDiplomaTopics().then(dt => { runInAction(() => { this.diplomaTopics = dt; }) + }).finally(() => { + ThinkService.completeAll(); }); } @@ -88,9 +97,13 @@ export class DiplomaTopicListPage extends Page { <> { this.fields.tableDescriptor && - } - /> + <> + } + filterModalState={this.fields.filterModalState} + /> + + } { @@ -105,12 +118,19 @@ export class DiplomaTopicListPage extends Page { const AdditionalButtons = observer(({state}: {state: DiplomaTopicPageState}) => { return ( -
- {(UserService.isSecretary || UserService.isAdministrator) && ( - + )} +
+
+ - )} +
); }); diff --git a/web/src/components/dictionary/DiplomaTopicModal.tsx b/web/src/components/dictionary/DiplomaTopicModal.tsx index 18d7366..383a0cf 100644 --- a/web/src/components/dictionary/DiplomaTopicModal.tsx +++ b/web/src/components/dictionary/DiplomaTopicModal.tsx @@ -16,6 +16,7 @@ import {StudentData} from "../../models/studentData"; import {PreparationDirection} from "../../models/preparationDirection"; import {DiplomaTopic} from "../../models/diplomaTopic"; import {TeacherData} from "../../models/teacherData"; +import {UserService} from "../../services/UserService"; const getAllTeachers = () => { return get("teacher-data/get-all"); @@ -26,7 +27,7 @@ const getAllPreparationDirections = () => { } class DiplomaTopicModalState { - constructor(modalState: ModalState, topic: DiplomaTopic) { + constructor(modalState: ModalState, topic?: DiplomaTopic, teacher?: TeacherData, dirPrep?: PreparationDirection) { makeObservable(this); this.modalState = modalState; getAllTeachers().then(teachers => { @@ -44,16 +45,35 @@ class DiplomaTopicModalState { this.editMode = false; this.adding = false; this.name.setAuto(topic.name); - this.teacher.setAuto([{ - value: _.toString(topic.id), - label: topic.name, - }]); - this.preparationDirection.setAuto([{ - value: _.toString(topic.preparationDirection.id), - label: topic.preparationDirection.name, - }]); + if (topic.teacher) { + this.teacher.setAuto([{ + value: _.toString(topic.teacher.id), + label: fullName(topic.teacher.participant), + }]); + } + if (topic.preparationDirection) { + this.preparationDirection.setAuto([{ + value: _.toString(topic.preparationDirection.id), + label: topic.preparationDirection.name, + }]); + } this.topicId = topic.id; } + + console.log("TEACH_3", teacher); + if (teacher) { + this.teacher.setAuto([{ + value: _.toString(teacher.id), + label: fullName(teacher.participant), + }]); + } + + if (dirPrep) { + this.preparationDirection.setAuto([{ + value: _.toString(dirPrep.id), + label: dirPrep.name, + }]); + } } @observable modalState: ModalState; @@ -98,30 +118,22 @@ class DiplomaTopicModalState { id: this.topicId, name: this.name.value, teacher: this.teacher.value && this.teacher.value[0] - ? this.getTeacherById(_.toNumber(this.teacher.value[0].value)) + ? { id: _.toNumber(this.teacher.value[0].value) } : undefined, preparationDirection: this.preparationDirection.value && this.preparationDirection.value[0] - ? this.getPreparationDirectionById(_.toNumber(this.preparationDirection.value[0].value)) + ? { id: _.toNumber(this.preparationDirection.value[0].value) } : undefined, }).then(() => { if (this.adding) NotificationService.success(`Тема ВКР ${this.name.value} успешно добавлена`); else NotificationService.success(`Тема ВКР ${this.name.value} успешно изменена`); - if (this.modalState) this.modalState.onApply(); + if (this.modalState && this.modalState.onApply) this.modalState.onApply(); }); this.modalState.close(); } - getTeacherById(id: number): TeacherData | undefined { - return this.allTeachers.find(teacher => teacher.id === id); - } - - getPreparationDirectionById(id: number): PreparationDirection | undefined { - return this.allPreparationDirections.find(direction => direction.id === id); - } - @action.bound changeEditMode() { this.editMode = !this.editMode; @@ -129,15 +141,25 @@ class DiplomaTopicModalState { } @observer -export default class DiplomaTopicModal extends ComponentContext<{ modalState: ModalState, topic?: DiplomaTopic}> { +export default class DiplomaTopicModal extends ComponentContext<{ modalState: ModalState, topic?: DiplomaTopic, teacher?: TeacherData, dirPrep?: PreparationDirection}> { constructor(props: any) { super(props); makeObservable(this); runInAction(() => { - this.fields = new DiplomaTopicModalState(props.modalState, props.topic); + console.log("TEACH_1", this.props.teacher); + this.fields = new DiplomaTopicModalState(props.modalState, props.topic, props.teacher, props.dirPrep); }); } + componentDidUpdate(prevProps: Readonly<{ modalState: ModalState; topic?: DiplomaTopic }>) { + if (prevProps.topic !== this.props.topic) { + runInAction(() => { + console.log("TEACH_2", this.props.teacher); + this.fields = new DiplomaTopicModalState(this.props.modalState, this.props.topic, this.props.teacher, this.props.dirPrep); + }); + } + } + @observable fields: DiplomaTopicModalState; render() { @@ -156,23 +178,28 @@ export default class DiplomaTopicModal extends ComponentContext<{ modalState: Mo possibleValues={this.fields.allTeachers?.map((value) => { return {label: fullName(value.participant), value: value.id?.toString() || ""}; })} - value={this.fields.teacher} disabled={!this.fields.editMode}/> + value={this.fields.teacher} disabled={!this.fields.editMode || this.props.teacher != undefined}/> { return {label: value.name, value: value.id?.toString() || ""}; })} - value={this.fields.preparationDirection} disabled={!this.fields.editMode}/> + value={this.fields.preparationDirection} disabled={!this.fields.editMode || this.props.dirPrep !== undefined}/> { - !this.fields.editMode && + !this.fields.editMode && (UserService.isSecretary || UserService.isAdministrator) && } { - this.fields.editMode && + this.fields.editMode && (UserService.isSecretary || UserService.isAdministrator || UserService.isTeacher) && } - + ); diff --git a/web/src/components/dictionary/PreparationDirectionList.tsx b/web/src/components/dictionary/PreparationDirectionList.tsx index 61cbf3b..5777200 100644 --- a/web/src/components/dictionary/PreparationDirectionList.tsx +++ b/web/src/components/dictionary/PreparationDirectionList.tsx @@ -7,12 +7,15 @@ import {Column, TableDescriptor} from "../../utils/tables"; import {PreparationDirection} from "../../models/preparationDirection"; import {get} from "../../utils/request"; import {datetimeConverter} from "../../utils/converters"; -import {Button} from "react-bootstrap"; +import {Button, Col, Row} from "react-bootstrap"; import {ModalState} from "../../utils/modalState"; import PreparationDirectionModal from "./PreparationDirectionModal"; import {UserService} from "../../services/UserService"; import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; import _ from "lodash"; +import {ThinkService} from "../../services/ThinkService"; +import {TeacherData} from "../../models/teacherData"; +import {fullName} from "../../models/participant"; const getAllPreparationDirections = () => { return get("prep-direction/get-all"); @@ -21,19 +24,16 @@ const getAllPreparationDirections = () => { class PreparationDirectionPageState { constructor() { makeObservable(this); - getAllPreparationDirections().then(pd => { - runInAction(() => { - this.preparationDirections = pd; - }) - }); - + this.updateDirectionOfPrep(); reaction(() => this.preparationDirections, () => { this.tableDescriptor = new TableDescriptor([ new Column('code', 'Код', undefined, (pd) => { - return ; }), new Column('name', 'Название'), + new Column('responsible', 'Ответственный за направление подготовки', + (teacher) => (teacher ? fullName(teacher.participant) : "Не назначен")), new Column('createdAt', 'Дата создания', datetimeConverter), new Column('updatedAt', 'Дата обновления', datetimeConverter), ], this.preparationDirections, true @@ -55,6 +55,18 @@ class PreparationDirectionPageState { this.preparationDirectionEditModalState.open(); } } + + @action.bound + updateDirectionOfPrep() { + ThinkService.think(); + getAllPreparationDirections().then(pd => { + runInAction(() => { + this.preparationDirections = pd; + }) + }).finally(() => { + ThinkService.completeAll(); + }); + } } @observer @@ -74,7 +86,7 @@ export class PreparationDirectionListPage extends Page { { this.fields.tableDescriptor && }/> + additionalButtons={}/> } { @@ -87,14 +99,21 @@ export class PreparationDirectionListPage extends Page { } } -const AdditionalButtons = observer(({state}: {state: PreparationDirectionPageState}) => { +const AdditionalButtons = observer(({state}: { state: PreparationDirectionPageState }) => { return ( -
- {(UserService.isSecretary || UserService.isAdministrator) && ( - + )} +
+
+ - )} +
); }); diff --git a/web/src/components/dictionary/PreparationDirectionModal.tsx b/web/src/components/dictionary/PreparationDirectionModal.tsx index d4a1d80..f352a3c 100644 --- a/web/src/components/dictionary/PreparationDirectionModal.tsx +++ b/web/src/components/dictionary/PreparationDirectionModal.tsx @@ -14,21 +14,40 @@ import {fullName, Participant} from "../../models/participant"; import {datetimeConverter} from "../../utils/converters"; import {StudentData} from "../../models/studentData"; import {PreparationDirection} from "../../models/preparationDirection"; +import {UserService} from "../../services/UserService"; +import { TeacherData } from "../../models/teacherData"; + +const getAllTeachers = () => { + return get("teacher-data/get-all"); +} + +const mapTeacher = (teacher: TeacherData) => { + return {value: _.toString(teacher.id), label: fullName(teacher.participant)} as SelectInputValue; +} class PreparationDirectionModalState { constructor(modalState: ModalState, direction?: PreparationDirection) { makeObservable(this); this.modalState = modalState; + getAllTeachers().then(td => { + runInAction(() => { + this.allTeachers = td.map(mapTeacher); + }); + }); + if (direction) { - console.log('PreparationDirectionModalState: initializing with direction', direction); this.name.setAuto(direction.name); this.code.setAuto(direction.code); this.id = direction.id; this.editMode = true; + if (direction.responsible) { + this.selectedTeacher.setAuto([mapTeacher(direction.responsible)]); + } } else { this.name.setAuto(""); this.code.setAuto(""); this.viewMode = false; + this.selectedTeacher.setAuto([]); } } @@ -39,6 +58,9 @@ class PreparationDirectionModalState { @observable viewMode = true; @observable id: number = undefined; + @observable allTeachers: SelectInputValue[] = []; + @observable selectedTeacher = new ReactiveValue(); + anyInvalid(): boolean { let invalid = this.name.invalid || this.code.invalid; if (!this.editMode) { @@ -63,7 +85,8 @@ class PreparationDirectionModalState { post("prep-direction/save", { id: this.id, name: this.name.value, - code: this.code.value + code: this.code.value, + responsible: this.selectedTeacher.value && this.selectedTeacher.value[0] ? { id: _.toNumber(this.selectedTeacher.value[0].value)} : undefined, }).then(() => { if (this.editMode) { NotificationService.success(`Направление подготовки ${this.code.value} успешно обновлено`); @@ -122,14 +145,17 @@ export default class PreparationDirectionModal extends ComponentContext<{ modalS + { - !this.fields.viewMode && + !this.fields.viewMode && (UserService.isSecretary || UserService.isAdministrator) && } { - this.fields.editMode && this.fields.viewMode && + this.fields.editMode && this.fields.viewMode && (UserService.isSecretary || UserService.isAdministrator) && } diff --git a/web/src/components/group/GroupListPage.tsx b/web/src/components/group/GroupListPage.tsx index 0a8036f..7a8769a 100644 --- a/web/src/components/group/GroupListPage.tsx +++ b/web/src/components/group/GroupListPage.tsx @@ -14,6 +14,7 @@ import _ from "lodash"; import {StudentData} from "../../models/studentData"; import GroupProfileModal from "./GroupProfileModal"; import {GroupListFilterModal} from "./GroupTableFilterModal"; +import {Button, Col, Row} from "react-bootstrap"; class GroupListPageState { constructor() { @@ -78,7 +79,8 @@ export class GroupListPage extends Page { <> { this.fields.tableDescriptor && - + }/> } { this.fields.selectedGroup && @@ -90,3 +92,15 @@ export class GroupListPage extends Page { } } + +const AdditionalButtons = observer(({state}: {state: GroupListPageState}) => { + return ( +
+
+ +
+
+ ); +}); diff --git a/web/src/components/group/GroupTableFilterModal.tsx b/web/src/components/group/GroupTableFilterModal.tsx index b15821e..936e747 100644 --- a/web/src/components/group/GroupTableFilterModal.tsx +++ b/web/src/components/group/GroupTableFilterModal.tsx @@ -6,6 +6,7 @@ import {action, makeObservable, observable, runInAction} from "mobx"; import {ReactiveValue} from "../../utils/reactive/reactiveValue"; import {Button, Modal, ModalBody, ModalFooter, ModalHeader, ModalTitle} from "react-bootstrap"; import {StringInput} from "../controls/ReactiveControls"; +import {DiplomaTopic} from "../../models/diplomaTopic"; export interface GroupListFilterProps { modalState: ModalState; @@ -20,21 +21,37 @@ export class GroupListFilterModal extends Component { runInAction(() => { this.filters.push(this.nameFilter); + this.filters.push(this.pdNameFilter); + this.filters.push(this.pdCodeFilter); }); } @observable filters = this.props.filters; @observable modalState = this.props.modalState; @observable nameField = new ReactiveValue().syncWithParam('name'); + @observable prepDirNameField = new ReactiveValue().syncWithParam('pdn'); + @observable prepDirCodeField = new ReactiveValue().syncWithParam('pdc'); @observable nameFilter = (group: Group) => { if (!this.nameField.value) return true; return group.name?.includes(this.nameField.value) }; + @observable pdNameFilter = (group: Group) => { + if (!this.prepDirNameField.value) return true; + return group.preparationDirection?.name?.includes(this.prepDirNameField.value) + } + + @observable pdCodeFilter = (group: Group) => { + if (!this.prepDirCodeField.value) return true; + return group.preparationDirection?.code?.includes(this.prepDirCodeField.value) + } + @action.bound reset() { this.nameField.set(""); + this.prepDirCodeField.set(""); + this.prepDirNameField.set(""); } render() { @@ -44,6 +61,8 @@ export class GroupListFilterModal extends Component { + + diff --git a/web/src/components/layout/Error.tsx b/web/src/components/layout/Error.tsx index 7c341f0..4849e97 100644 --- a/web/src/components/layout/Error.tsx +++ b/web/src/components/layout/Error.tsx @@ -4,6 +4,9 @@ import {Page} from "./Page"; @observer export class Error extends Page { get page() { - return

Error

+ return
+

Произошла ошибка!

+

Обратитесь к администратору

+
} } diff --git a/web/src/components/layout/Header.tsx b/web/src/components/layout/Header.tsx index 9e33dad..3bf6763 100644 --- a/web/src/components/layout/Header.tsx +++ b/web/src/components/layout/Header.tsx @@ -1,18 +1,20 @@ -import {Container, Nav, Navbar, NavDropdown} from "react-bootstrap"; +import {Container, Nav, Navbar, NavbarText, NavDropdown} from "react-bootstrap"; import {RouterLink} from "mobx-state-router"; import {observer} from "mobx-react"; -import {post} from "../../utils/request"; -import {UserLoginModal} from "../user/UserLoginModal"; +import {get, post} from "../../utils/request"; import {ModalState} from "../../utils/modalState"; -import {action, makeObservable, observable} from "mobx"; +import {action, makeObservable, observable, runInAction} from "mobx"; import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; import {ComponentContext} from "../../utils/ComponentContext"; import {NotificationService} from "../../services/NotificationService"; import {ThinkService} from "../../services/ThinkService"; import ParticipantProfileModal from "../participant/ParticipantProfileModal"; -import GroupProfileModal from "../group/GroupProfileModal"; import {fullName} from "../../models/participant"; import {UserService} from "../../services/UserService"; +import GroupProfileModal from "../group/GroupProfileModal"; +import {DefenseInfoModal} from "../defence/DefenseInfoModal"; +import {UserLoginModal} from "../user/UserLoginModal"; +import {RouterService} from "../../services/RouterService"; @observer export class Header extends ComponentContext { @@ -24,6 +26,7 @@ export class Header extends ComponentContext { @observable loginModalState = new ModalState(); @observable addGroupModalState = new ModalState(); @observable userRegistrationModalState = new ModalState(); + @observable defenseAddModalState = new ModalState(); render() { let userThink = ThinkService.isThinking('updateCurrentUser'); @@ -33,7 +36,7 @@ export class Header extends ComponentContext { - TDMS + ВКР