From 5a6d19b8dabe6bb52e2fd0337e1c3b191cf64b38 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dominik=20Pil=C3=A1r?= <xpilar3@fi.muni.cz>
Date: Wed, 24 Feb 2021 10:37:05 +0100
Subject: [PATCH] Evaluation of questionnaires completed.

---
 .../adaptive/controller/PhasesController.java |   2 -
 .../adaptive/controller/TasksController.java  |   2 -
 .../TrainingRunsRestController.java           |  30 ++---
 .../domain/phase/questions/Question.java      |  15 +++
 .../phase/questions/QuestionAnswer.java       |  20 +++-
 .../phase/questions/QuestionAnswerId.java     |  17 ++-
 .../phase/questions/QuestionPhaseResult.java  |  16 +--
 .../adaptive/domain/training/TrainingRun.java |  22 ----
 .../adaptive/facade/TrainingRunFacade.java    |  42 +++----
 .../repository/QuestionAnswerRepository.java  |   4 +-
 .../QuestionPhaseResultRepository.java        |   2 +-
 .../repository/phases/QuestionRepository.java |   4 +
 .../QuestionnaireEvaluationService.java       | 107 ++++++++++++------
 .../service/training/TrainingRunService.java  |  11 +-
 .../V1__db_adaptive_trainings_schema.sql      |  19 ++++
 .../V2__db_adaptive_trainings_sequences.sql   |   1 +
 .../AdaptiveTrainingApplicationTests.java     |   2 +-
 17 files changed, 188 insertions(+), 128 deletions(-)

diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/controller/PhasesController.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/controller/PhasesController.java
index ab9fe856..86d35479 100644
--- a/src/main/java/cz/muni/ics/kypo/training/adaptive/controller/PhasesController.java
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/controller/PhasesController.java
@@ -21,8 +21,6 @@ import java.util.List;
 
 @RestController
 @RequestMapping(value = "/training-definitions/{definitionId}/phases", produces = MediaType.APPLICATION_JSON_VALUE)
-@CrossOrigin(origins = "*", allowCredentials = "true", allowedHeaders = "*",
-        methods = {RequestMethod.GET, RequestMethod.POST, RequestMethod.DELETE, RequestMethod.PUT})
 @Api(value = "/training-definitions/{definitionId}/phase",
         tags = "Phases",
         consumes = MediaType.APPLICATION_JSON_VALUE,
diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/controller/TasksController.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/controller/TasksController.java
index d3ad7e44..c939708a 100644
--- a/src/main/java/cz/muni/ics/kypo/training/adaptive/controller/TasksController.java
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/controller/TasksController.java
@@ -15,8 +15,6 @@ import javax.validation.Valid;
 
 @RestController
 @RequestMapping(value = "/training-definitions/{definitionId}/phases/{phaseId}/tasks", produces = MediaType.APPLICATION_JSON_VALUE)
-@CrossOrigin(origins = "*", allowCredentials = "true", allowedHeaders = "*",
-        methods = {RequestMethod.GET, RequestMethod.POST, RequestMethod.DELETE, RequestMethod.PUT})
 @Api(value = "/training-definitions/{definitionId}/tasks",
         tags = "Tasks",
         consumes = MediaType.APPLICATION_JSON_VALUE,
diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/controller/TrainingRunsRestController.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/controller/TrainingRunsRestController.java
index 83fe2059..f3c7250a 100644
--- a/src/main/java/cz/muni/ics/kypo/training/adaptive/controller/TrainingRunsRestController.java
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/controller/TrainingRunsRestController.java
@@ -11,6 +11,7 @@ import cz.muni.ics.kypo.training.adaptive.dto.AbstractPhaseDTO;
 import cz.muni.ics.kypo.training.adaptive.dto.IsCorrectAnswerDTO;
 import cz.muni.ics.kypo.training.adaptive.dto.UserRefDTO;
 import cz.muni.ics.kypo.training.adaptive.dto.responses.PageResultResource;
+import cz.muni.ics.kypo.training.adaptive.dto.run.QuestionnairePhaseAnswersDTO;
 import cz.muni.ics.kypo.training.adaptive.dto.training.ValidateAnswerDTO;
 import cz.muni.ics.kypo.training.adaptive.dto.trainingrun.AccessTrainingRunDTO;
 import cz.muni.ics.kypo.training.adaptive.dto.trainingrun.AccessedTrainingRunDTO;
@@ -358,30 +359,21 @@ public class TrainingRunsRestController {
         return ResponseEntity.ok().build();
     }
 
-    /**
-     * Evaluate responses to questionnaire.
-     *
-     * @param runId     id of training run.
-     * @param responses to questionnaire
-     * @return the response entity
-     */
     @ApiOperation(httpMethod = "PUT",
-            value = "Evaluate responses to questionnaire",
-            nickname = "evaluateResponsesToQuestionnaire",
+            value = "Evaluate answers to a questionnaire phase",
+            nickname = "evaluateAnswersToQuestionnaire",
             produces = MediaType.APPLICATION_JSON_VALUE
     )
     @ApiResponses(value = {
-            @ApiResponse(code = 204, message = "The responses to questionnaire has been evaluated and stored."),
-            @ApiResponse(code = 404, message = "The training run has not been found.", response = ApiError.class),
-            @ApiResponse(code = 409, message = "Current phase of training is not questionnaire phase or phase has been already answered.", response = ApiError.class),
-            @ApiResponse(code = 500, message = "Unexpected condition was encountered.", response = ApiError.class)
+            @ApiResponse(code = 200, message = "Answers evaluated"),
+            @ApiResponse(code = 500, message = "Unexpected application error")
     })
-    @PutMapping(value = "/{runId}/questionnaire-evaluations", produces = MediaType.APPLICATION_JSON_VALUE)
-    public ResponseEntity<Void> evaluateResponsesToQuestionnaire(@ApiParam(value = "Training run ID", required = true)
-                                                                 @PathVariable("runId") Long runId,
-                                                                 @ApiParam(value = "Responses to questionnaire", required = true)
-                                                                 @RequestBody String responses) {
-        trainingRunFacade.evaluateResponsesToQuestionnaire(runId, responses);
+    @PutMapping(value = "/{runId}/questionnaire-evaluation", produces = MediaType.APPLICATION_JSON_VALUE)
+    public ResponseEntity<Void> evaluateAnswersToQuestionnaire(@ApiParam(value = "Training run ID", required = true)
+                                                               @PathVariable("runId") Long runId,
+                                                               @ApiParam(value = "Responses to questionnaire", required = true)
+                                                               @Valid @RequestBody QuestionnairePhaseAnswersDTO questionnairePhaseAnswersDTO) {
+        trainingRunFacade.evaluateAnswersToQuestionnaire(runId, questionnairePhaseAnswersDTO);
         return ResponseEntity.noContent().build();
     }
 
diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/phase/questions/Question.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/phase/questions/Question.java
index 9edf7931..cc6db3e3 100644
--- a/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/phase/questions/Question.java
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/phase/questions/Question.java
@@ -95,4 +95,19 @@ public class Question implements Serializable {
     public void addQuestionPhaseRelation(QuestionPhaseRelation questionPhaseRelation) {
         this.questionPhaseRelations.add(questionPhaseRelation);
     }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof Question)) return false;
+        Question question = (Question) o;
+        return getOrder() == question.getOrder() &&
+                getQuestionType() == question.getQuestionType() &&
+                Objects.equals(getText(), question.getText());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(getQuestionType(), getText(), getOrder());
+    }
 }
diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/phase/questions/QuestionAnswer.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/phase/questions/QuestionAnswer.java
index 211b904f..1e5a94ba 100644
--- a/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/phase/questions/QuestionAnswer.java
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/phase/questions/QuestionAnswer.java
@@ -1,9 +1,12 @@
-package cz.muni.ics.kypo.training.adaptive.domain;
+package cz.muni.ics.kypo.training.adaptive.domain.phase.questions;
+
+import cz.muni.ics.kypo.training.adaptive.domain.UserRef;
 
 import javax.persistence.EmbeddedId;
 import javax.persistence.Entity;
 import javax.persistence.Table;
 import java.io.Serializable;
+import java.util.Objects;
 
 @Entity
 @Table(name = "question_answer")
@@ -11,7 +14,6 @@ public class QuestionAnswer implements Serializable {
 
     @EmbeddedId
     private QuestionAnswerId questionAnswerId;
-
     private String answer;
 
     public QuestionAnswerId getQuestionAnswerId() {
@@ -29,4 +31,18 @@ public class QuestionAnswer implements Serializable {
     public void setAnswer(String answer) {
         this.answer = answer;
     }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof QuestionAnswer)) return false;
+        QuestionAnswer that = (QuestionAnswer) o;
+        return getQuestionAnswerId().equals(that.getQuestionAnswerId()) &&
+                getAnswer().equals(that.getAnswer());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(getQuestionAnswerId(), getAnswer());
+    }
 }
diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/phase/questions/QuestionAnswerId.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/phase/questions/QuestionAnswerId.java
index d59dff5c..71b48ba6 100644
--- a/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/phase/questions/QuestionAnswerId.java
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/phase/questions/QuestionAnswerId.java
@@ -1,11 +1,19 @@
-package cz.muni.ics.kypo.training.adaptive.domain;
+package cz.muni.ics.kypo.training.adaptive.domain.phase.questions;
+
+import cz.muni.ics.kypo.training.adaptive.domain.TRAcquisitionLock;
 
 import javax.persistence.Embeddable;
+import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
 import java.io.Serializable;
 import java.util.Objects;
 
 @Embeddable
 public class QuestionAnswerId implements Serializable {
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "question_id")
     private Question question;
     private Long trainingRunId;
 
@@ -36,10 +44,11 @@ public class QuestionAnswerId implements Serializable {
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
+        if (!(o instanceof QuestionAnswerId))
+            return false;
         QuestionAnswerId that = (QuestionAnswerId) o;
-        return Objects.equals(question, that.question) &&
-                Objects.equals(trainingRunId, that.trainingRunId);
+        return getQuestion().equals(that.getQuestion()) &&
+                getTrainingRunId().equals(that.getTrainingRunId());
     }
 
     @Override
diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/phase/questions/QuestionPhaseResult.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/phase/questions/QuestionPhaseResult.java
index cfc2bf0d..034a1659 100644
--- a/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/phase/questions/QuestionPhaseResult.java
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/phase/questions/QuestionPhaseResult.java
@@ -1,14 +1,7 @@
-package cz.muni.ics.kypo.training.adaptive.domain;
-
-import javax.persistence.Column;
-import javax.persistence.Entity;
-import javax.persistence.FetchType;
-import javax.persistence.GeneratedValue;
-import javax.persistence.GenerationType;
-import javax.persistence.Id;
-import javax.persistence.ManyToOne;
-import javax.persistence.SequenceGenerator;
-import javax.persistence.Table;
+package cz.muni.ics.kypo.training.adaptive.domain.phase.questions;
+
+
+import javax.persistence.*;
 import java.io.Serializable;
 
 @Entity
@@ -25,6 +18,7 @@ public class QuestionPhaseResult implements Serializable {
     private int achievedResult;
 
     @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "question_phase_relation_id")
     private QuestionPhaseRelation questionPhaseRelation;
 
     public Long getId() {
diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/training/TrainingRun.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/training/TrainingRun.java
index 95acdfb7..42a8bc2b 100644
--- a/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/training/TrainingRun.java
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/domain/training/TrainingRun.java
@@ -120,10 +120,6 @@ public class TrainingRun extends AbstractEntity<Long> {
     @ManyToOne(fetch = FetchType.LAZY)
     @JoinColumn(name = "user_ref_id", nullable = false)
     private UserRef participantRef;
-    @Lob
-    @Type(type = "org.hibernate.type.TextType")
-    @Column(name = "questionnaire_responses", nullable = true)
-    private String questionnaireResponses;
     @Column(name = "phase_answered")
     private boolean phaseAnswered;
     @Column(name = "previous_sandbox_instance_ref_id")
@@ -304,24 +300,6 @@ public class TrainingRun extends AbstractEntity<Long> {
         this.solutionTaken = solutionTaken;
     }
 
-    /**
-     * Gets responses of current assessment phase
-     *
-     * @return the assessment responses
-     */
-    public String getQuestionnaireResponses() {
-        return questionnaireResponses;
-    }
-
-    /**
-     * Sets responses of current assessment phase
-     *
-     * @param assessmentResponses the assessment responses
-     */
-    public void setQuestionnaireResponses(String assessmentResponses) {
-        this.questionnaireResponses = assessmentResponses;
-    }
-
     /**
      * Gets DB reference of trainee
      *
diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/facade/TrainingRunFacade.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/facade/TrainingRunFacade.java
index 980b96fb..dbc54add 100644
--- a/src/main/java/cz/muni/ics/kypo/training/adaptive/facade/TrainingRunFacade.java
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/facade/TrainingRunFacade.java
@@ -20,6 +20,8 @@ import cz.muni.ics.kypo.training.adaptive.dto.questionnaire.QuestionChoiceDTO;
 import cz.muni.ics.kypo.training.adaptive.dto.questionnaire.QuestionDTO;
 import cz.muni.ics.kypo.training.adaptive.dto.questionnaire.QuestionnairePhaseDTO;
 import cz.muni.ics.kypo.training.adaptive.dto.responses.PageResultResource;
+import cz.muni.ics.kypo.training.adaptive.dto.run.QuestionAnswerDTO;
+import cz.muni.ics.kypo.training.adaptive.dto.run.QuestionnairePhaseAnswersDTO;
 import cz.muni.ics.kypo.training.adaptive.dto.trainingrun.AccessTrainingRunDTO;
 import cz.muni.ics.kypo.training.adaptive.dto.trainingrun.AccessedTrainingRunDTO;
 import cz.muni.ics.kypo.training.adaptive.dto.trainingrun.TrainingRunByIdDTO;
@@ -28,8 +30,7 @@ import cz.muni.ics.kypo.training.adaptive.enums.Actions;
 import cz.muni.ics.kypo.training.adaptive.enums.PhaseType;
 import cz.muni.ics.kypo.training.adaptive.mapping.mapstruct.PhaseMapper;
 import cz.muni.ics.kypo.training.adaptive.mapping.mapstruct.TrainingRunMapper;
-import cz.muni.ics.kypo.training.adaptive.service.SecurityService;
-import cz.muni.ics.kypo.training.adaptive.service.UserService;
+import cz.muni.ics.kypo.training.adaptive.service.QuestionnaireEvaluationService;
 import cz.muni.ics.kypo.training.adaptive.service.api.UserManagementServiceApi;
 import cz.muni.ics.kypo.training.adaptive.service.training.TrainingRunService;
 import org.slf4j.Logger;
@@ -43,10 +44,8 @@ import org.springframework.transaction.annotation.Transactional;
 
 import java.time.Clock;
 import java.time.LocalDateTime;
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Optional;
+import java.util.*;
+import java.util.stream.Collectors;
 
 /**
  * The type Training run facade.
@@ -56,29 +55,29 @@ public class TrainingRunFacade {
 
     private static final Logger LOG = LoggerFactory.getLogger(TrainingRunFacade.class);
 
-    private TrainingRunService trainingRunService;
-    private UserService userService;
-    private UserManagementServiceApi userManagementServiceApi;
-    private TrainingRunMapper trainingRunMapper;
-    private PhaseMapper phaseMapper;
+    private final TrainingRunService trainingRunService;
+    private final UserManagementServiceApi userManagementServiceApi;
+    private final TrainingRunMapper trainingRunMapper;
+    private final PhaseMapper phaseMapper;
+    private final QuestionnaireEvaluationService questionnaireEvaluationService;
+
 
 
     /**
      * Instantiates a new Training run facade.
      *
      * @param trainingRunService the training run service
-     * @param userService        the user service
      * @param trainingRunMapper  the training run mapper
      * @param phaseMapper        the phase mapper
      */
     @Autowired
     public TrainingRunFacade(TrainingRunService trainingRunService,
-                             UserService userService,
+                             QuestionnaireEvaluationService questionnaireEvaluationService,
                              UserManagementServiceApi userManagementServiceApi,
                              TrainingRunMapper trainingRunMapper,
                              PhaseMapper phaseMapper) {
         this.trainingRunService = trainingRunService;
-        this.userService = userService;
+        this.questionnaireEvaluationService = questionnaireEvaluationService;
         this.userManagementServiceApi = userManagementServiceApi;
         this.trainingRunMapper = trainingRunMapper;
         this.phaseMapper = phaseMapper;
@@ -334,15 +333,18 @@ public class TrainingRunFacade {
     }
 
     /**
-     * Evaluate and store responses to questionnaire.
+     * Evaluate and store answers to questionnaire.
      *
-     * @param trainingRunId     id of Training Run to be finish.
-     * @param responsesAsString responses to questionnaire
+     * @param trainingRunId     id of the Training Run.
+     * @param questionnairePhaseAnswersDTO answers to questionnaire
      */
-    @IsTrainee
+    @PreAuthorize("hasAuthority(T(cz.muni.ics.kypo.training.adaptive.enums.RoleTypeSecurity).ROLE_ADAPTIVE_TRAINING_ADMINISTRATOR)" +
+            "or @securityService.isTraineeOfGivenTrainingRun(#trainingRunId)")
     @TransactionalWO
-    public void evaluateResponsesToQuestionnaire(Long trainingRunId, String responsesAsString) {
-        trainingRunService.evaluateResponsesToQuestionnaire(trainingRunId, responsesAsString);
+    public void evaluateAnswersToQuestionnaire(Long trainingRunId, QuestionnairePhaseAnswersDTO questionnairePhaseAnswersDTO) {
+        Map<Long, String> questionsAnswersMapping = questionnairePhaseAnswersDTO.getAnswers().stream()
+                .collect(Collectors.toMap(QuestionAnswerDTO::getQuestionId, QuestionAnswerDTO::getAnswer));
+        questionnaireEvaluationService.saveAndEvaluateAnswersToQuestionnaire(trainingRunId, questionsAnswersMapping);
     }
 
     /**
diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/QuestionAnswerRepository.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/QuestionAnswerRepository.java
index 2c5e54e8..8fc46323 100644
--- a/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/QuestionAnswerRepository.java
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/QuestionAnswerRepository.java
@@ -1,7 +1,7 @@
 package cz.muni.ics.kypo.training.adaptive.repository;
 
-import cz.muni.ics.kypo.training.adaptive.domain.QuestionAnswer;
-import cz.muni.ics.kypo.training.adaptive.domain.QuestionAnswerId;
+import cz.muni.ics.kypo.training.adaptive.domain.phase.questions.QuestionAnswer;
+import cz.muni.ics.kypo.training.adaptive.domain.phase.questions.QuestionAnswerId;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.stereotype.Repository;
 
diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/QuestionPhaseResultRepository.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/QuestionPhaseResultRepository.java
index 86c9945d..6c417b06 100644
--- a/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/QuestionPhaseResultRepository.java
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/QuestionPhaseResultRepository.java
@@ -1,6 +1,6 @@
 package cz.muni.ics.kypo.training.adaptive.repository;
 
-import cz.muni.ics.kypo.training.adaptive.domain.QuestionPhaseResult;
+import cz.muni.ics.kypo.training.adaptive.domain.phase.questions.QuestionPhaseResult;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.stereotype.Repository;
 
diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/phases/QuestionRepository.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/phases/QuestionRepository.java
index 2e2ceb2b..c6aad8d6 100644
--- a/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/phases/QuestionRepository.java
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/repository/phases/QuestionRepository.java
@@ -4,6 +4,10 @@ import cz.muni.ics.kypo.training.adaptive.domain.phase.questions.Question;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.stereotype.Repository;
 
+import java.util.Optional;
+
 @Repository
 public interface QuestionRepository extends JpaRepository<Question, Long> {
+
+    Optional<Question> findByIdAndQuestionnairePhaseId(Long questionId, Long questionnaireId);
 }
diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/service/QuestionnaireEvaluationService.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/service/QuestionnaireEvaluationService.java
index c1d70ef7..ad097254 100644
--- a/src/main/java/cz/muni/ics/kypo/training/adaptive/service/QuestionnaireEvaluationService.java
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/service/QuestionnaireEvaluationService.java
@@ -1,33 +1,29 @@
 package cz.muni.ics.kypo.training.adaptive.service;
 
-import cz.muni.ics.kypo.training.adaptive.domain.Question;
-import cz.muni.ics.kypo.training.adaptive.domain.QuestionAnswer;
-import cz.muni.ics.kypo.training.adaptive.domain.QuestionAnswerId;
-import cz.muni.ics.kypo.training.adaptive.domain.QuestionChoice;
-import cz.muni.ics.kypo.training.adaptive.domain.QuestionPhaseRelation;
-import cz.muni.ics.kypo.training.adaptive.domain.QuestionPhaseResult;
-import cz.muni.ics.kypo.training.adaptive.dto.run.QuestionAnswerDTO;
-import cz.muni.ics.kypo.training.adaptive.dto.run.QuestionnairePhaseAnswersDTO;
+import cz.muni.ics.kypo.training.adaptive.domain.phase.QuestionnairePhase;
+import cz.muni.ics.kypo.training.adaptive.domain.phase.questions.*;
+import cz.muni.ics.kypo.training.adaptive.domain.training.TrainingRun;
+import cz.muni.ics.kypo.training.adaptive.enums.QuestionnaireType;
+import cz.muni.ics.kypo.training.adaptive.exceptions.BadRequestException;
+import cz.muni.ics.kypo.training.adaptive.exceptions.EntityConflictException;
+import cz.muni.ics.kypo.training.adaptive.exceptions.EntityErrorDetail;
+import cz.muni.ics.kypo.training.adaptive.exceptions.EntityNotFoundException;
 import cz.muni.ics.kypo.training.adaptive.repository.QuestionAnswerRepository;
-import cz.muni.ics.kypo.training.adaptive.repository.QuestionPhaseRelationRepository;
 import cz.muni.ics.kypo.training.adaptive.repository.QuestionPhaseResultRepository;
-import cz.muni.ics.kypo.training.adaptive.repository.QuestionRepository;
+import cz.muni.ics.kypo.training.adaptive.repository.phases.QuestionPhaseRelationRepository;
+import cz.muni.ics.kypo.training.adaptive.repository.phases.QuestionRepository;
+import cz.muni.ics.kypo.training.adaptive.repository.training.TrainingRunRepository;
+import cz.muni.ics.kypo.training.adaptive.service.audit.AuditEventsService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.CollectionUtils;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.Set;
+import java.util.*;
 import java.util.stream.Collectors;
 
 @Service
-@Transactional
 public class QuestionnaireEvaluationService {
 
     private static final Logger LOG = LoggerFactory.getLogger(QuestionnaireEvaluationService.class);
@@ -36,35 +32,70 @@ public class QuestionnaireEvaluationService {
     private final QuestionAnswerRepository questionAnswerRepository;
     private final QuestionPhaseRelationRepository questionPhaseRelationRepository;
     private final QuestionPhaseResultRepository questionPhaseResultRepository;
+    private final TrainingRunRepository trainingRunRepository;
+    private final AuditEventsService auditEventsService;
 
     @Autowired
-    public QuestionnaireEvaluationService(QuestionRepository questionRepository, QuestionAnswerRepository questionAnswerRepository,
-                                          QuestionPhaseRelationRepository questionPhaseRelationRepository, QuestionPhaseResultRepository questionPhaseResultRepository) {
+    public QuestionnaireEvaluationService(QuestionRepository questionRepository,
+                                          QuestionAnswerRepository questionAnswerRepository,
+                                          QuestionPhaseRelationRepository questionPhaseRelationRepository,
+                                          QuestionPhaseResultRepository questionPhaseResultRepository,
+                                          TrainingRunRepository trainingRunRepository,
+                                          AuditEventsService auditEventsService) {
         this.questionRepository = questionRepository;
         this.questionAnswerRepository = questionAnswerRepository;
         this.questionPhaseRelationRepository = questionPhaseRelationRepository;
         this.questionPhaseResultRepository = questionPhaseResultRepository;
+        this.trainingRunRepository = trainingRunRepository;
+        this.auditEventsService = auditEventsService;
     }
 
-    public List<QuestionAnswer> saveAnswersToQuestionnaire(Long runId, QuestionnairePhaseAnswersDTO questionnairePhaseAnswersDTO) {
-        List<QuestionAnswer> result = new ArrayList<>();
-        for (QuestionAnswerDTO answer : questionnairePhaseAnswersDTO.getAnswers()) {
-            Question question = questionRepository.findById(answer.getQuestionId())
-                    .orElseThrow(() -> new RuntimeException("Question was not found"));
-            // TODO throw proper exception once kypo2-training is migrated
-
-            QuestionAnswer questionAnswer = new QuestionAnswer();
-            questionAnswer.setQuestionAnswerId(new QuestionAnswerId(question, runId));
-            questionAnswer.setAnswer(answer.getAnswer());
+    /**
+     * Evaluate and store answers to questionnaire.
+     *
+     * @param trainingRunId     id of training run to answer the questionnaire.
+     * @param questionnairePhaseAnswers answers to questionnaire to be evaluated. Map of entries (Key = question ID, Value = answer).
+     * @throws EntityNotFoundException training run is not found.
+     */
+    public List<QuestionAnswer> saveAndEvaluateAnswersToQuestionnaire(Long trainingRunId, Map<Long, String> questionnairePhaseAnswers) {
+        TrainingRun trainingRun = this.findByIdWithPhase(trainingRunId);
+        this.checkIfCanBeEvaluated(trainingRun);
+
+        List<QuestionAnswer> storedQuestionsAnswers = new ArrayList<>();
+        for (Map.Entry<Long, String> questionAnswer : questionnairePhaseAnswers.entrySet()) {
+            Long questionnaireId = trainingRun.getCurrentPhase().getId();
+            storedQuestionsAnswers.add(saveQuestionAnswer(questionAnswer.getKey(), questionAnswer.getValue(), questionnaireId, trainingRun.getId()));
+        }
+        if (((QuestionnairePhase) trainingRun.getCurrentPhase()).getQuestionnaireType() == QuestionnaireType.ADAPTIVE) {
+            this.evaluateAnswersToQuestionnaire(trainingRunId, storedQuestionsAnswers);
+        }
+        auditEventsService.auditPhaseCompletedAction(trainingRun);
+        auditEventsService.auditQuestionnaireAnswersAction(trainingRun, storedQuestionsAnswers.stream()
+                .collect(Collectors.toMap(questionAnswer -> questionAnswer.getQuestionAnswerId().getQuestion().getText(), QuestionAnswer::getAnswer))
+                .toString());
+        trainingRun.setPhaseAnswered(true);
+        return storedQuestionsAnswers;
+    }
 
-            questionAnswerRepository.save(questionAnswer);
-            result.add(questionAnswer);
+    private void checkIfCanBeEvaluated(TrainingRun trainingRun) {
+        if (!(trainingRun.getCurrentPhase() instanceof QuestionnairePhase)) {
+            throw new BadRequestException("Current phase is not questionnaire phase and cannot be evaluated.");
         }
+        if (trainingRun.isPhaseAnswered())
+            throw new EntityConflictException(new EntityErrorDetail(TrainingRun.class, "id", Long.class, trainingRun.getId(),
+                    "Current phase of the training run has been already answered."));
+    }
 
-        return result;
+    private QuestionAnswer saveQuestionAnswer(Long questionId, String answer, Long questionnaireId, Long trainingRunId) {
+        Question question = this.findQuestionByIdAndQuestionnairePhaseId(questionId, questionnaireId);
+        QuestionAnswer questionAnswer = new QuestionAnswer();
+        questionAnswer.setQuestionAnswerId(new QuestionAnswerId(question, trainingRunId));
+        questionAnswer.setAnswer(answer);
+
+        return questionAnswerRepository.save(questionAnswer);
     }
 
-    public void evaluateAnswersToQuestionnaire(Long runId, List<QuestionAnswer> answers) {
+    private void evaluateAnswersToQuestionnaire(Long runId, List<QuestionAnswer> answers) {
         if (CollectionUtils.isEmpty(answers)) {
             return;
         }
@@ -118,4 +149,14 @@ public class QuestionnaireEvaluationService {
                 .filter(q -> Objects.equals(answer, q.getText()))
                 .findFirst();
     }
+
+    private TrainingRun findByIdWithPhase(Long runId) {
+        return trainingRunRepository.findByIdWithPhase(runId).orElseThrow(() -> new EntityNotFoundException(
+                new EntityErrorDetail(TrainingRun.class, "id", runId.getClass(), runId)));
+    }
+
+    private Question findQuestionByIdAndQuestionnairePhaseId(Long questionId, Long questionnaireId) {
+        return questionRepository.findByIdAndQuestionnairePhaseId(questionId, questionnaireId)
+                .orElseThrow(() -> new EntityNotFoundException(new EntityErrorDetail(Question.class, "id", Long.class, questionId)));
+    }
 }
diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/service/training/TrainingRunService.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/service/training/TrainingRunService.java
index f1924170..88d7e2ca 100644
--- a/src/main/java/cz/muni/ics/kypo/training/adaptive/service/training/TrainingRunService.java
+++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/service/training/TrainingRunService.java
@@ -60,7 +60,7 @@ public class TrainingRunService {
      * @param trainingInstanceRepository  the training instance repository
      * @param participantRefRepository    the participant ref repository
      * @param auditEventsService          the audit events service
-     * @param securityService             the security service
+     * @param sandboxServiceApi           the sandbox service api
      * @param trAcquisitionLockRepository the tr acquisition lock repository
      */
     @Autowired
@@ -322,7 +322,6 @@ public class TrainingRunService {
         } else {
             newTrainingRun.setParticipantRef(participantRefRepository.save(new UserRef(userManagementServiceApi.getLoggedInUserRefId())));
         }
-        newTrainingRun.setQuestionnaireResponses("[]");
         newTrainingRun.setState(TRState.RUNNING);
         newTrainingRun.setTrainingInstance(trainingInstance);
         newTrainingRun.setStartTime(startTime);
@@ -501,13 +500,7 @@ public class TrainingRunService {
         trainingRunRepository.save(trainingRun);
     }
 
-    /**
-     * Evaluate and store responses to questionnaire.
-     *
-     * @param trainingRunId     id of training run to be finished.
-     * @param responsesAsString response to questionnaire to be evaluated
-     * @throws EntityNotFoundException training run is not found.
-     */
+
     public void evaluateResponsesToQuestionnaire(Long trainingRunId, String responsesAsString) {
         TrainingRun trainingRun = findByIdWithPhase(trainingRunId);
         if (!(trainingRun.getCurrentPhase() instanceof QuestionnairePhase)) {
diff --git a/src/main/resources/db/migration/V1__db_adaptive_trainings_schema.sql b/src/main/resources/db/migration/V1__db_adaptive_trainings_schema.sql
index 43bbe615..14d59308 100644
--- a/src/main/resources/db/migration/V1__db_adaptive_trainings_schema.sql
+++ b/src/main/resources/db/migration/V1__db_adaptive_trainings_schema.sql
@@ -172,6 +172,25 @@ create table question_phase_relation_question (
     foreign key (question_id) references question
 );
 
+create table question_answer (
+    question_id  int8 not null,
+    training_run_id int8 not null,
+    answer varchar(255) not null,
+    primary key (question_id, training_run_id),
+    foreign key (question_id) references question,
+    foreign key (training_run_id) references training_run,
+    unique (question_id, training_run_id)
+);
+
+create table question_phase_result (
+    question_phase_result_id  bigserial not null,
+    training_run_id int8 not null,
+    question_phase_relation_id int8 not null,
+    achieved_result int4 not null,
+    primary key (question_phase_result_id),
+    foreign key (question_phase_relation_id) references question_phase_relation
+);
+
 -- ACCESS TOKEN
 create table access_token (
    id  bigserial not null,
diff --git a/src/main/resources/db/migration/V2__db_adaptive_trainings_sequences.sql b/src/main/resources/db/migration/V2__db_adaptive_trainings_sequences.sql
index 5f55c926..1f09c3ec 100644
--- a/src/main/resources/db/migration/V2__db_adaptive_trainings_sequences.sql
+++ b/src/main/resources/db/migration/V2__db_adaptive_trainings_sequences.sql
@@ -4,3 +4,4 @@ CREATE SEQUENCE question_seq AS bigint INCREMENT 50 MINVALUE 1;
 CREATE SEQUENCE question_choice_seq AS bigint INCREMENT 50 MINVALUE 1;
 CREATE SEQUENCE question_phase_seq AS bigint INCREMENT 50 MINVALUE 1;
 CREATE SEQUENCE decision_matrix_row_seq AS bigint INCREMENT 50 MINVALUE 1;
+CREATE SEQUENCE question_phase_result_seq AS bigint INCREMENT 50 MINVALUE 1;
diff --git a/src/test/java/cz/muni/ics/kypo/training/adaptive/AdaptiveTrainingApplicationTests.java b/src/test/java/cz/muni/ics/kypo/training/adaptive/AdaptiveTrainingApplicationTests.java
index 53d7b0d9..df77eecf 100644
--- a/src/test/java/cz/muni/ics/kypo/training/adaptive/AdaptiveTrainingApplicationTests.java
+++ b/src/test/java/cz/muni/ics/kypo/training/adaptive/AdaptiveTrainingApplicationTests.java
@@ -4,7 +4,7 @@ import org.junit.jupiter.api.Test;
 import org.springframework.boot.test.context.SpringBootTest;
 
 @SpringBootTest(classes = TestAdaptiveTrainingConfiguration.class)
-class DemoApplicationTests {
+class AdaptiveTrainingApplicationTests {
 
     @Test
     void contextLoads() {
-- 
GitLab