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