From 5b2559b61a75cea7c385c75745f35fd91c0656e0 Mon Sep 17 00:00:00 2001 From: Jan Tymel <410388@mail.muni.cz> Date: Fri, 5 Feb 2021 07:18:48 +0100 Subject: [PATCH] Fix some issues with questionnaire model Related to #7, #3 --- .../demo/domain/QuestionPhaseRelation.java | 19 +++- .../demo/domain/QuestionnairePhase.java | 2 +- .../example/demo/dto/AbstractQuestionDto.java | 10 +- .../demo/dto/QuestionPhaseRelationDto.java | 26 +++-- .../QuestionPhaseRelationRepository.java | 12 +++ .../service/QuestionnairePhaseService.java | 94 ++++++++++++++----- 6 files changed, 120 insertions(+), 43 deletions(-) create mode 100644 src/main/java/com/example/demo/repository/QuestionPhaseRelationRepository.java diff --git a/src/main/java/com/example/demo/domain/QuestionPhaseRelation.java b/src/main/java/com/example/demo/domain/QuestionPhaseRelation.java index c368b936..e76be3f6 100644 --- a/src/main/java/com/example/demo/domain/QuestionPhaseRelation.java +++ b/src/main/java/com/example/demo/domain/QuestionPhaseRelation.java @@ -23,11 +23,14 @@ public class QuestionPhaseRelation { private Integer order; @ManyToOne(fetch = FetchType.LAZY) - private QuestionnairePhase relatedPhase; + private QuestionnairePhase questionnairePhase; + + @ManyToOne(fetch = FetchType.LAZY) + private TrainingPhase relatedPhase; @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST) @JoinTable(name = "question_phase_relation_question", - joinColumns = @JoinColumn(name = "question_phase_id"), + joinColumns = @JoinColumn(name = "question_phase_relation_id"), inverseJoinColumns = @JoinColumn(name = "question_id") ) private Set<Question> questions; @@ -50,11 +53,19 @@ public class QuestionPhaseRelation { this.order = order; } - public QuestionnairePhase getRelatedPhase() { + public QuestionnairePhase getQuestionnairePhase() { + return questionnairePhase; + } + + public void setQuestionnairePhase(QuestionnairePhase questionnairePhase) { + this.questionnairePhase = questionnairePhase; + } + + public TrainingPhase getRelatedPhase() { return relatedPhase; } - public void setRelatedPhase(QuestionnairePhase relatedPhase) { + public void setRelatedPhase(TrainingPhase relatedPhase) { this.relatedPhase = relatedPhase; } diff --git a/src/main/java/com/example/demo/domain/QuestionnairePhase.java b/src/main/java/com/example/demo/domain/QuestionnairePhase.java index aaea9259..1bced338 100644 --- a/src/main/java/com/example/demo/domain/QuestionnairePhase.java +++ b/src/main/java/com/example/demo/domain/QuestionnairePhase.java @@ -23,7 +23,7 @@ public class QuestionnairePhase extends AbstractPhase { private List<Question> questions = new ArrayList<>(); @OrderBy - @OneToMany(mappedBy = "relatedPhase", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) + @OneToMany(mappedBy = "questionnairePhase", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) private List<QuestionPhaseRelation> questionPhaseRelations = new ArrayList<>(); public List<Question> getQuestions() { diff --git a/src/main/java/com/example/demo/dto/AbstractQuestionDto.java b/src/main/java/com/example/demo/dto/AbstractQuestionDto.java index e5482f00..32bb7b43 100644 --- a/src/main/java/com/example/demo/dto/AbstractQuestionDto.java +++ b/src/main/java/com/example/demo/dto/AbstractQuestionDto.java @@ -20,7 +20,7 @@ public abstract class AbstractQuestionDto implements Serializable { @ApiModelProperty(value = "It defines the type of the question", allowableValues = "FFQ, MCQ, RFQ", required = true, example = "MCQ") @NotNull(message = "Question type must be specified") - private QuestionType type; + private QuestionType questionType; @ApiModelProperty(value = "Choices that are distributed with the question", required = true) private List<QuestionChoiceDto> choices; @@ -41,12 +41,12 @@ public abstract class AbstractQuestionDto implements Serializable { this.text = text; } - public QuestionType getType() { - return type; + public QuestionType getQuestionType() { + return questionType; } - public void setType(QuestionType type) { - this.type = type; + public void setQuestionType(QuestionType type) { + this.questionType = type; } public List<QuestionChoiceDto> getChoices() { diff --git a/src/main/java/com/example/demo/dto/QuestionPhaseRelationDto.java b/src/main/java/com/example/demo/dto/QuestionPhaseRelationDto.java index ccf3290d..a409ff19 100644 --- a/src/main/java/com/example/demo/dto/QuestionPhaseRelationDto.java +++ b/src/main/java/com/example/demo/dto/QuestionPhaseRelationDto.java @@ -2,7 +2,6 @@ package com.example.demo.dto; import io.swagger.annotations.ApiModelProperty; -import javax.validation.Valid; import javax.validation.constraints.Max; import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; @@ -17,9 +16,12 @@ public class QuestionPhaseRelationDto { @NotNull(message = "Question order must be specified") private Integer order; - @Valid - @ApiModelProperty(value = "Set of questions related to the specified questionnaire") - private Set<QuestionRequiredIdDto> questions; + @ApiModelProperty(value = "Set of IDs of questions related to the specified questionnaire") + private Set<Long> questionIds; + + @ApiModelProperty(value = "ID of training phase to which the questions are related of question", required = true, example = "1") + @NotNull(message = "Phase ID in question-phase relations must not be null") + private Long phaseId; @ApiModelProperty(value = "Percentage that defines whether a player was successful or not ", required = true, example = "50") @Min(value = 0, message = "Success rate must not be lower than 0 %") @@ -42,12 +44,20 @@ public class QuestionPhaseRelationDto { this.order = order; } - public Set<QuestionRequiredIdDto> getQuestions() { - return questions; + public Set<Long> getQuestionIds() { + return questionIds; + } + + public void setQuestionIds(Set<Long> questionIds) { + this.questionIds = questionIds; + } + + public Long getPhaseId() { + return phaseId; } - public void setQuestions(Set<QuestionRequiredIdDto> questions) { - this.questions = questions; + public void setPhaseId(Long phaseId) { + this.phaseId = phaseId; } public int getSuccessRate() { diff --git a/src/main/java/com/example/demo/repository/QuestionPhaseRelationRepository.java b/src/main/java/com/example/demo/repository/QuestionPhaseRelationRepository.java new file mode 100644 index 00000000..b4853fe2 --- /dev/null +++ b/src/main/java/com/example/demo/repository/QuestionPhaseRelationRepository.java @@ -0,0 +1,12 @@ +package com.example.demo.repository; + +import com.example.demo.domain.QuestionPhaseRelation; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface QuestionPhaseRelationRepository extends JpaRepository<QuestionPhaseRelation, Long> { + + @Query("SELECT COALESCE(MAX(q.order), -1) FROM QuestionPhaseRelation q WHERE q.questionnairePhase.id = :phaseId") + Integer getCurrentMaxOrder(@Param("phaseId") Long phaseId); +} diff --git a/src/main/java/com/example/demo/service/QuestionnairePhaseService.java b/src/main/java/com/example/demo/service/QuestionnairePhaseService.java index 7576a6ff..20ba97f9 100644 --- a/src/main/java/com/example/demo/service/QuestionnairePhaseService.java +++ b/src/main/java/com/example/demo/service/QuestionnairePhaseService.java @@ -1,22 +1,31 @@ package com.example.demo.service; import com.example.demo.domain.Question; +import com.example.demo.domain.QuestionPhaseRelation; import com.example.demo.domain.QuestionnairePhase; +import com.example.demo.domain.TrainingPhase; import com.example.demo.dto.PhaseCreateDTO; +import com.example.demo.dto.QuestionPhaseRelationDto; import com.example.demo.dto.QuestionnairePhaseDto; import com.example.demo.dto.QuestionnaireUpdateDto; import com.example.demo.enums.PhaseType; import com.example.demo.enums.QuestionnaireType; import com.example.demo.mapper.BeanMapper; import com.example.demo.repository.AbstractPhaseRepository; +import com.example.demo.repository.QuestionPhaseRelationRepository; +import com.example.demo.repository.QuestionRepository; import com.example.demo.repository.QuestionnairePhaseRepository; +import com.example.demo.repository.TrainingPhaseRepository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; -import java.util.Optional; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Set; @Service public class QuestionnairePhaseService { @@ -29,9 +38,18 @@ public class QuestionnairePhaseService { @Autowired private AbstractPhaseRepository abstractPhaseRepository; + @Autowired + private QuestionRepository questionRepository; + + @Autowired + private TrainingPhaseRepository trainingPhaseRepository; + + @Autowired + private QuestionPhaseRelationRepository questionPhaseRelationRepository; + public QuestionnairePhaseDto createDefaultQuestionnairePhase(Long trainingDefinitionId, PhaseCreateDTO phaseCreateDTO) { - QuestionnairePhase questionnairePhase =new QuestionnairePhase(); + QuestionnairePhase questionnairePhase = new QuestionnairePhase(); questionnairePhase.setTitle("Title of questionnaire level"); questionnairePhase.setTrainingDefinitionId(trainingDefinitionId); questionnairePhase.setOrder(abstractPhaseRepository.getCurrentMaxOrder(trainingDefinitionId) + 1); @@ -47,24 +65,6 @@ public class QuestionnairePhaseService { return BeanMapper.INSTANCE.toDto(persistedEntity); } - public QuestionnairePhaseDto updateQuestion(QuestionnairePhase questionnairePhase) { - Optional<QuestionnairePhase> persistedQuestion = questionnairePhaseRepository.findById(questionnairePhase.getId()); - - if (persistedQuestion.isEmpty()) { - // TODO return 404 - LOG.error("No questionnaire level found with ID {}.", questionnairePhase.getId()); - return new QuestionnairePhaseDto(); - } - - questionnairePhase.setTrainingDefinitionId(persistedQuestion.get().getTrainingDefinitionId()); - questionnairePhase.setQuestions(persistedQuestion.get().getQuestions()); - questionnairePhase.setOrder(persistedQuestion.get().getOrder()); - - QuestionnairePhase savedEntity = questionnairePhaseRepository.save(questionnairePhase); - - return BeanMapper.INSTANCE.toDto(savedEntity); - } - public QuestionnairePhaseDto updateQuestionnairePhase(Long definitionId, Long phaseId, QuestionnaireUpdateDto questionnaireUpdateDto) { QuestionnairePhase questionnairePhase = BeanMapper.INSTANCE.toEntity(questionnaireUpdateDto); questionnairePhase.setId(phaseId); @@ -75,13 +75,11 @@ public class QuestionnairePhaseService { // TODO add check to trainingDefinitionId and phaseId (field structure will be probably changed); - questionnairePhase.setTrainingDefinitionId(persistedQuestionnairePhase.getTrainingDefinitionId()); questionnairePhase.setOrder(persistedQuestionnairePhase.getOrder()); - if (!CollectionUtils.isEmpty(questionnairePhase.getQuestionPhaseRelations())) { - questionnairePhase.getQuestionPhaseRelations().forEach(x -> x.setRelatedPhase(questionnairePhase)); - } + questionnairePhase.getQuestionPhaseRelations().clear(); + questionnairePhase.getQuestionPhaseRelations().addAll(resolveQuestionnairePhaseRelationsUpdate(questionnairePhase, questionnaireUpdateDto)); if (!CollectionUtils.isEmpty(questionnairePhase.getQuestions())) { questionnairePhase.getQuestions().forEach(x -> x.setQuestionnairePhase(questionnairePhase)); @@ -92,6 +90,52 @@ public class QuestionnairePhaseService { QuestionnairePhase savedEntity = questionnairePhaseRepository.save(questionnairePhase); - return BeanMapper.INSTANCE.toDto(savedEntity); + QuestionnairePhaseDto result = BeanMapper.INSTANCE.toDto(savedEntity); + if (QuestionnaireType.ADAPTIVE.equals(savedEntity.getQuestionnaireType())) { + result.setPhaseType(PhaseType.QUESTIONNAIRE_ADAPTIVE); + } else { + result.setPhaseType(PhaseType.QUESTIONNAIRE_GENERAL); + } + + return result; } + + private List<QuestionPhaseRelation> resolveQuestionnairePhaseRelationsUpdate(QuestionnairePhase questionnairePhase, QuestionnaireUpdateDto questionnaireUpdateDto) { + List<QuestionPhaseRelation> questionnairePhaseRelations = new ArrayList<>(); + + if (!CollectionUtils.isEmpty(questionnaireUpdateDto.getPhaseRelations())) { + int order = 0; + for (QuestionPhaseRelationDto phaseRelation : questionnaireUpdateDto.getPhaseRelations()) { + Set<Question> questionsInPhaseRelation = Set.copyOf(questionRepository.findAllById(phaseRelation.getQuestionIds())); + + QuestionPhaseRelation questionPhaseRelation; + if (Objects.isNull(phaseRelation.getId())) { + questionPhaseRelation = new QuestionPhaseRelation(); + questionPhaseRelation.setQuestions(questionsInPhaseRelation); + } else { + questionPhaseRelation = questionPhaseRelationRepository.findById(phaseRelation.getId()) + .orElseThrow(() -> new RuntimeException("Question phase relation was not found")); + // TODO throw proper exception once kypo2-training is migrated + + questionPhaseRelation.getQuestions().clear(); + questionPhaseRelation.getQuestions().addAll(questionsInPhaseRelation); + } + + TrainingPhase trainingPhase = trainingPhaseRepository.findById(phaseRelation.getPhaseId()) + .orElseThrow(() -> new RuntimeException("Training phase was not found")); + // TODO throw proper exception once kypo2-training is migrated + + questionPhaseRelation.setOrder(order); + questionPhaseRelation.setSuccessRate(phaseRelation.getSuccessRate()); + questionPhaseRelation.setRelatedPhase(trainingPhase); + questionPhaseRelation.setQuestionnairePhase(questionnairePhase); + + questionnairePhaseRelations.add(questionPhaseRelation); + order++; + } + } + + return questionnairePhaseRelations; + } + } -- GitLab