From 7100c18aefbd1da0b1b15db996fba0e23fd93e82 Mon Sep 17 00:00:00 2001
From: srosse <stephane.rosse@frentix.com>
Date: Tue, 7 Jul 2020 13:17:16 +0200
Subject: [PATCH] OO-4788: option to make user's score visible or not after
 correction

---
 .../assessment/ui/tool/AssessmentForm.java    | 15 ++++++++--
 .../olat/course/nodes/IQTESTCourseNode.java   |  8 +++++
 .../nodes/iq/IQConfigurationController.java   | 20 ++++++++++---
 .../course/nodes/iq/IQEditController.java     |  4 +++
 .../IQIdentityListCourseNodeController.java   |  8 ++++-
 .../olat/course/nodes/iq/QTI21EditForm.java   | 29 +++++++++++++++++++
 .../nodes/iq/_i18n/LocalStrings_de.properties |  3 ++
 .../nodes/iq/_i18n/LocalStrings_en.properties |  3 ++
 .../java/org/olat/ims/qti21/QTI21Module.java  | 13 +++++++++
 .../ims/qti21/ui/QTI21AdminController.java    | 24 ++++++++++++++-
 .../ui/QTI21AssessmentDetailsController.java  |  6 +++-
 .../qti21/ui/_i18n/LocalStrings_de.properties |  3 ++
 .../qti21/ui/_i18n/LocalStrings_en.properties |  3 ++
 .../ui/GradingAssignmentsListController.java  |  6 +++-
 14 files changed, 134 insertions(+), 11 deletions(-)

diff --git a/src/main/java/org/olat/course/assessment/ui/tool/AssessmentForm.java b/src/main/java/org/olat/course/assessment/ui/tool/AssessmentForm.java
index 937829f1286..b50a3148843 100644
--- a/src/main/java/org/olat/course/assessment/ui/tool/AssessmentForm.java
+++ b/src/main/java/org/olat/course/assessment/ui/tool/AssessmentForm.java
@@ -89,12 +89,15 @@ public class AssessmentForm extends FormBasicController {
 	private TextElement score;
 	private IntegerElement attempts;
 	private StaticTextElement cutVal;
-	private SingleSelection passed, userVisibility;
-	private TextElement userComment, coachComment;
+	private SingleSelection passed;
+	private SingleSelection userVisibility;
+	private TextElement userComment;
+	private TextElement coachComment;
 	private FormLayoutContainer docsLayoutCont;
 	private FileElement uploadDocsEl;
 	private FormSubmit submitButton;
-	private FormLink saveAndDoneLink, reopenLink;
+	private FormLink reopenLink;
+	private FormLink saveAndDoneLink;
 	private List<DocumentWrapper> assessmentDocuments;
 	
 	private DialogBoxController confirmDeleteDocCtrl;
@@ -445,6 +448,12 @@ public class AssessmentForm extends FormBasicController {
 			userComment.setValue(userCommentValue);
 		}
 		
+		if(scoreEval.getUserVisible() == null || scoreEval.getUserVisible().booleanValue()) {
+			userVisibility.select(userVisibilityKeys[0], true);
+		} else {
+			userVisibility.select(userVisibilityKeys[1], true);
+		}
+		
 		reloadAssessmentDocs();
 		updateStatus(scoreEval);
 	}
diff --git a/src/main/java/org/olat/course/nodes/IQTESTCourseNode.java b/src/main/java/org/olat/course/nodes/IQTESTCourseNode.java
index 76dd7d61805..ad517944464 100644
--- a/src/main/java/org/olat/course/nodes/IQTESTCourseNode.java
+++ b/src/main/java/org/olat/course/nodes/IQTESTCourseNode.java
@@ -102,6 +102,7 @@ import org.olat.ims.qti.statistics.QTIStatisticResourceResult;
 import org.olat.ims.qti.statistics.QTIStatisticSearchParams;
 import org.olat.ims.qti21.AssessmentTestSession;
 import org.olat.ims.qti21.QTI21DeliveryOptions;
+import org.olat.ims.qti21.QTI21Module;
 import org.olat.ims.qti21.QTI21Service;
 import org.olat.ims.qti21.manager.AssessmentTestSessionDAO;
 import org.olat.ims.qti21.manager.archive.QTI21ArchiveFormat;
@@ -269,6 +270,13 @@ public class IQTESTCourseNode extends AbstractAccessableCourseNode implements QT
 		return timeLimit;
 	}
 	
+	public boolean isScoreVisibleAfterCorrection() {
+		String defVisibility = CoreSpringFactory.getImpl(QTI21Module.class).isResultsVisibleAfterCorrectionWorkflow()
+				? IQEditController.CONFIG_VALUE_SCORE_VISIBLE_AFTER_CORRECTION : IQEditController.CONFIG_VALUE_SCORE_NOT_VISIBLE_AFTER_CORRECTION;
+		String visibility = getModuleConfiguration().getStringValue(IQEditController.CONFIG_KEY_SCORE_VISIBILITY_AFTER_CORRECTION, defVisibility);
+		return IQEditController.CONFIG_VALUE_SCORE_VISIBLE_AFTER_CORRECTION.equals(visibility);
+	}
+	
 	public AssessmentTest loadAssessmentTest(RepositoryEntry testEntry) {
 		if(testEntry == null) return null;
 		
diff --git a/src/main/java/org/olat/course/nodes/iq/IQConfigurationController.java b/src/main/java/org/olat/course/nodes/iq/IQConfigurationController.java
index c9c1e64cab4..29f1eef3e7f 100644
--- a/src/main/java/org/olat/course/nodes/iq/IQConfigurationController.java
+++ b/src/main/java/org/olat/course/nodes/iq/IQConfigurationController.java
@@ -69,8 +69,9 @@ import org.olat.ims.qti.fileresource.TestFileResource;
 import org.olat.ims.qti.process.AssessmentInstance;
 import org.olat.ims.qti21.AssessmentTestSession;
 import org.olat.ims.qti21.QTI21DeliveryOptions;
-import org.olat.ims.qti21.QTI21Service;
 import org.olat.ims.qti21.QTI21DeliveryOptions.PassedType;
+import org.olat.ims.qti21.QTI21Module;
+import org.olat.ims.qti21.QTI21Service;
 import org.olat.ims.qti21.model.InMemoryOutcomeListener;
 import org.olat.ims.qti21.model.xml.AssessmentTestBuilder;
 import org.olat.ims.qti21.ui.AssessmentTestDisplayController;
@@ -133,6 +134,8 @@ public class IQConfigurationController extends BasicController {
 	@Autowired
 	private QTIModule qtiModule;
 	@Autowired
+	private QTI21Module qti21Module;
+	@Autowired
 	private QTI21Service qti21service;
 	@Autowired
 	private GradingService gradingService;
@@ -226,12 +229,21 @@ public class IQConfigurationController extends BasicController {
 			}
 			QTI21DeliveryOptions deliveryOptions = qti21service.getDeliveryOptions(re);
 			if(replacedTest) {// set some default settings in case the user don't save the next panel
+				String correctionMode;
 				if(gradingService.isGradingEnabled(re, null)) {
-					moduleConfiguration.setStringValue(IQEditController.CONFIG_CORRECTION_MODE, IQEditController.CORRECTION_GRADING);
+					correctionMode = IQEditController.CORRECTION_GRADING;
 				} else if(needManualCorrection || getPassedType(re, deliveryOptions) == PassedType.manually) {
-					moduleConfiguration.setStringValue(IQEditController.CONFIG_CORRECTION_MODE, IQEditController.CORRECTION_MANUAL);
+					correctionMode = IQEditController.CORRECTION_MANUAL;
+				} else {
+					correctionMode = IQEditController.CORRECTION_AUTO;
+				}
+				moduleConfiguration.setStringValue(IQEditController.CONFIG_CORRECTION_MODE, correctionMode);
+				if(IQEditController.CORRECTION_GRADING.equals(correctionMode) ||  IQEditController.CORRECTION_MANUAL.equals(correctionMode)) {
+					String userVisible = qti21Module.isResultsVisibleAfterCorrectionWorkflow()
+							? IQEditController.CONFIG_VALUE_SCORE_VISIBLE_AFTER_CORRECTION : IQEditController.CONFIG_VALUE_SCORE_NOT_VISIBLE_AFTER_CORRECTION;
+					moduleConfiguration.setStringValue(IQEditController.CONFIG_KEY_SCORE_VISIBILITY_AFTER_CORRECTION, userVisible);
 				} else {
-					moduleConfiguration.setStringValue(IQEditController.CONFIG_CORRECTION_MODE, IQEditController.CORRECTION_AUTO);
+					moduleConfiguration.remove(IQEditController.CONFIG_KEY_SCORE_VISIBILITY_AFTER_CORRECTION);
 				}
 				fireEvent(ureq, NodeEditController.NODECONFIG_CHANGED_EVENT);
 			}
diff --git a/src/main/java/org/olat/course/nodes/iq/IQEditController.java b/src/main/java/org/olat/course/nodes/iq/IQEditController.java
index ee318a982f8..5b200937b88 100644
--- a/src/main/java/org/olat/course/nodes/iq/IQEditController.java
+++ b/src/main/java/org/olat/course/nodes/iq/IQEditController.java
@@ -129,6 +129,10 @@ public class IQEditController extends ActivateableTabbableDefaultController impl
 	public static final String CONFIG_FULLWINDOW = "fullwindow";
 	/** Enable manual correction */
 	public static final String CONFIG_CORRECTION_MODE = "correctionMode";
+	/** Set score visibility after correction */
+	public static final String CONFIG_KEY_SCORE_VISIBILITY_AFTER_CORRECTION = "scoreVisibilityAfterCorrection";
+	public static final String CONFIG_VALUE_SCORE_VISIBLE_AFTER_CORRECTION = "visible";
+	public static final String CONFIG_VALUE_SCORE_NOT_VISIBLE_AFTER_CORRECTION = "notvisible";
 	/** Test in full window mode*/
 	public static final String CONFIG_ALLOW_ANONYM = "allowAnonym";
 	/** Digitally signed the assessment results */
diff --git a/src/main/java/org/olat/course/nodes/iq/IQIdentityListCourseNodeController.java b/src/main/java/org/olat/course/nodes/iq/IQIdentityListCourseNodeController.java
index c181c478b86..423655506c0 100644
--- a/src/main/java/org/olat/course/nodes/iq/IQIdentityListCourseNodeController.java
+++ b/src/main/java/org/olat/course/nodes/iq/IQIdentityListCourseNodeController.java
@@ -450,6 +450,8 @@ public class IQIdentityListCourseNodeController extends IdentityListCourseNodeCo
 		CourseEnvironment courseEnv = getCourseEnvironment();
 		RepositoryEntry testEntry = getReferencedRepositoryEntry();
 		AssessmentConfig assessmentConfig = courseAssessmentService.getAssessmentConfig(courseNode);
+		
+		boolean userVisibleAfter = ((IQTESTCourseNode)courseNode).isScoreVisibleAfterCorrection();
 
 		for(AssessmentTestSession testSession:testSessionsToComplete) {
 			UserCourseEnvironment assessedUserCourseEnv = AssessmentHelper
@@ -464,8 +466,12 @@ public class IQIdentityListCourseNodeController extends IdentityListCourseNodeCo
 				passed = Boolean.valueOf(calculated);
 			}
 			AssessmentEntryStatus finalStatus = status == null ? scoreEval.getAssessmentStatus() : status;
+			Boolean userVisible = scoreEval.getUserVisible();
+			if(finalStatus == AssessmentEntryStatus.done) {
+				userVisible = Boolean.valueOf(userVisibleAfter);
+			}
 			ScoreEvaluation manualScoreEval = new ScoreEvaluation(score, passed,
-					finalStatus, scoreEval.getUserVisible(), scoreEval.getCurrentRunCompletion(),
+					finalStatus, userVisible, scoreEval.getCurrentRunCompletion(),
 					scoreEval.getCurrentRunStatus(), testSession.getKey());
 			courseAssessmentService.updateScoreEvaluation(courseNode, manualScoreEval, assessedUserCourseEnv,
 					getIdentity(), false, Role.coach);
diff --git a/src/main/java/org/olat/course/nodes/iq/QTI21EditForm.java b/src/main/java/org/olat/course/nodes/iq/QTI21EditForm.java
index 023bd88f5e6..c0a9a546968 100644
--- a/src/main/java/org/olat/course/nodes/iq/QTI21EditForm.java
+++ b/src/main/java/org/olat/course/nodes/iq/QTI21EditForm.java
@@ -46,6 +46,7 @@ import org.olat.ims.qti.process.AssessmentInstance;
 import org.olat.ims.qti21.QTI21AssessmentResultsOptions;
 import org.olat.ims.qti21.QTI21DeliveryOptions;
 import org.olat.ims.qti21.QTI21DeliveryOptions.PassedType;
+import org.olat.ims.qti21.QTI21Module;
 import org.olat.ims.qti21.QTI21Service;
 import org.olat.ims.qti21.model.xml.AssessmentTestBuilder;
 import org.olat.modules.ModuleConfiguration;
@@ -84,6 +85,7 @@ public class QTI21EditForm extends FormBasicController {
 	private final String[] dateValues = new String[dateKeys.length];
 
 	private SingleSelection correctionModeEl;
+	private SingleSelection scoreVisibilityAfterCorrectionEl;
 	private SingleSelection showResultsDateDependentEl;
 	private MultipleSelectionElement scoreInfo;
 	private DateChooser generalEndDateElement;
@@ -111,6 +113,8 @@ public class QTI21EditForm extends FormBasicController {
 	
 	private DialogBoxController confirmTestDateCtrl;
 
+	@Autowired
+	private QTI21Module qtiModule;
 	@Autowired
 	private QTI21Service qtiService;
 	@Autowired
@@ -212,6 +216,19 @@ public class QTI21EditForm extends FormBasicController {
 				correctionModeEl.select(correctionModeKeys[0], true);
 			}
 		}
+		
+		KeyValues visibilityKeyValues = new KeyValues();
+		visibilityKeyValues.add(KeyValues.entry(IQEditController.CONFIG_VALUE_SCORE_VISIBLE_AFTER_CORRECTION, translate("results.visibility.after.correction.visible")));
+		visibilityKeyValues.add(KeyValues.entry(IQEditController.CONFIG_VALUE_SCORE_NOT_VISIBLE_AFTER_CORRECTION, translate("results.visibility.after.correction.not.visible")));
+		scoreVisibilityAfterCorrectionEl = uifactory.addRadiosVertical("results.visibility.after.correction", "results.visibility.after.correction", formLayout,
+				visibilityKeyValues.keys(), visibilityKeyValues.values());
+		String defVisibility = qtiModule.isResultsVisibleAfterCorrectionWorkflow()
+				? IQEditController.CONFIG_VALUE_SCORE_VISIBLE_AFTER_CORRECTION : IQEditController.CONFIG_VALUE_SCORE_NOT_VISIBLE_AFTER_CORRECTION;
+		String visibility = modConfig.getStringValue(IQEditController.CONFIG_KEY_SCORE_VISIBILITY_AFTER_CORRECTION, defVisibility);
+		if(visibilityKeyValues.containsKey(visibility)) {
+			scoreVisibilityAfterCorrectionEl.select(visibility, true);
+		}
+		updateScoreVisibility();
 	}
 
 	protected void initFormReport(FormItemContainer formLayout) {
@@ -387,10 +404,17 @@ public class QTI21EditForm extends FormBasicController {
 			} else {
 				update();
 			}
+		} else if(correctionModeEl == source) {
+			updateScoreVisibility();
 		}
 		super.formInnerEvent(ureq, source, event);
 	}
 	
+	private void updateScoreVisibility() {
+		String correctionMode = correctionModeEl.getSelectedKey();
+		scoreVisibilityAfterCorrectionEl.setVisible(correctionMode.equals(IQEditController.CORRECTION_MANUAL) ||  correctionMode.equals(IQEditController.CORRECTION_GRADING));
+	}
+	
 	private void update() {
 		assessmentResultsOnFinishEl.setVisible(showResultsOnFinishEl.isSelected(0) || !showResultsDateDependentEl.isSelected(0));
 		
@@ -553,6 +577,11 @@ public class QTI21EditForm extends FormBasicController {
 		if(correctionModeEl.isOneSelected()) {
 			modConfig.setStringValue(IQEditController.CONFIG_CORRECTION_MODE, correctionModeEl.getSelectedKey());
 		}
+		if(scoreVisibilityAfterCorrectionEl.isOneSelected() && scoreVisibilityAfterCorrectionEl.isVisible()) {
+			modConfig.setStringValue(IQEditController.CONFIG_KEY_SCORE_VISIBILITY_AFTER_CORRECTION, scoreVisibilityAfterCorrectionEl.getSelectedKey());
+		} else {
+			modConfig.remove(IQEditController.CONFIG_KEY_SCORE_VISIBILITY_AFTER_CORRECTION);
+		}
 
 		modConfig.setBooleanEntry(IQEditController.CONFIG_KEY_ENABLESCOREINFO, scoreInfo.isSelected(0));
 		modConfig.setStringValue(IQEditController.CONFIG_KEY_DATE_DEPENDENT_RESULTS, showResultsDateDependentEl.getSelectedKey());
diff --git a/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_de.properties
index 593fb35df56..527bd3b5733 100644
--- a/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_de.properties
@@ -181,6 +181,9 @@ report.config=Report
 reset.test.data.title=Daten von Test zur\u00FCcksetzen
 retrieve.tests.title=$org.olat.ims.qti.statistics.ui\:menu.pull.tests.title
 retrievetest.confirm.text=$org.olat.ims.qti\:retrievetest.confirm.text
+results.visibility.after.correction=Sichtbarkeit von Punkte nach Korrektur
+results.visibility.after.correction.not.visible=Nicht sichtbar
+results.visibility.after.correction.visible=Sichtbar
 score.cut=Notwendige Punktzahl f\u00FCr "Bestanden"
 score.max=Punktemaximum
 score.min=Punkteminimum
diff --git a/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_en.properties
index 367c8c6df88..dda05daef0a 100644
--- a/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_en.properties
@@ -182,6 +182,9 @@ report.config=Report
 reset.test.data.title=Reset data of test
 retrieve.tests.title=$org.olat.ims.qti.statistics.ui\:menu.pull.tests.title
 retrievetest.confirm.text=$org.olat.ims.qti\:retrievetest.confirm.text
+results.visibility.after.correction=Visibility of points after correction
+results.visibility.after.correction.not.visible=Not visible
+results.visibility.after.correction.visible=Visible
 score.cut=Passed cut value
 score.max=Maximum score
 score.min=Minimum score
diff --git a/src/main/java/org/olat/ims/qti21/QTI21Module.java b/src/main/java/org/olat/ims/qti21/QTI21Module.java
index dd565d1247c..5af3b44de13 100644
--- a/src/main/java/org/olat/ims/qti21/QTI21Module.java
+++ b/src/main/java/org/olat/ims/qti21/QTI21Module.java
@@ -62,6 +62,8 @@ public class QTI21Module extends AbstractSpringModule {
 	private String digitalSignatureCertificatePassword;
 	@Value("${qti21.correction.workflow:anonymous}")
 	private String correctionWorkflow;
+	@Value("${qti21.results.visible.after.correction:true}")
+	private String resultsVisibleAfterCorrectionWorkflow;
 	@Value("${qti21.import.encoding.fallback:}")
 	private String importEncodingFallback;
 	
@@ -92,6 +94,8 @@ public class QTI21Module extends AbstractSpringModule {
 			correctionWorkflow = CorrectionWorkflow.valueOf(correctionWorkflowObj).name();
 		}
 		
+		resultsVisibleAfterCorrectionWorkflow = getStringPropertyValue("qti21.results.visible.after.correction", resultsVisibleAfterCorrectionWorkflow);
+		
 		String digitalSignatureObj = getStringPropertyValue("digital.signature", true);
 		if(StringHelper.containsNonWhitespace(digitalSignatureObj)) {
 			digitalSignatureEnabled = "enabled".equals(digitalSignatureObj);
@@ -126,6 +130,15 @@ public class QTI21Module extends AbstractSpringModule {
 		this.correctionWorkflow = correctionWorkflow.name();
 		setStringProperty("qti21.correction.workflow", correctionWorkflow.name(), true);
 	}
+	
+	public boolean isResultsVisibleAfterCorrectionWorkflow() {
+		return "true".equals(resultsVisibleAfterCorrectionWorkflow);
+	}
+	
+	public void setResultsVisibleAfterCorrectionWorkflow(boolean visible) {
+		resultsVisibleAfterCorrectionWorkflow = visible ? "true" : "false";
+		setStringProperty("qti21.results.visible.after.correction", resultsVisibleAfterCorrectionWorkflow, true);
+	}
 
 	public boolean isDigitalSignatureEnabled() {
 		return digitalSignatureEnabled;
diff --git a/src/main/java/org/olat/ims/qti21/ui/QTI21AdminController.java b/src/main/java/org/olat/ims/qti21/ui/QTI21AdminController.java
index c7f69709f01..30e54831ad3 100644
--- a/src/main/java/org/olat/ims/qti21/ui/QTI21AdminController.java
+++ b/src/main/java/org/olat/ims/qti21/ui/QTI21AdminController.java
@@ -27,6 +27,7 @@ import org.olat.core.gui.components.form.flexible.FormItemContainer;
 import org.olat.core.gui.components.form.flexible.elements.FileElement;
 import org.olat.core.gui.components.form.flexible.elements.FormLink;
 import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement;
+import org.olat.core.gui.components.form.flexible.elements.SingleSelection;
 import org.olat.core.gui.components.form.flexible.elements.TextElement;
 import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
 import org.olat.core.gui.components.form.flexible.impl.FormEvent;
@@ -57,11 +58,13 @@ public class QTI21AdminController extends FormBasicController {
 
 	private static final String[] onKeys = new String[]{ "on" };
 	private static final String[] onValues = new String[]{ "" };
+	private static final String[] visibilityKeys = new String[] { "visible", "not-visible" };
 	
 	private FormLink validationButton;
 	private MultipleSelectionElement mathExtensionEl;
 	private MultipleSelectionElement digitalSignatureEl;
 	private MultipleSelectionElement anonymCorrectionWorkflowEl;
+	private SingleSelection resultsVisibilityAfterCorrectionEl;
 	private FileElement certificateEl;
 	private TextElement certificatePasswordEl;
 	
@@ -111,7 +114,18 @@ public class QTI21AdminController extends FormBasicController {
 		if(qti21Module.getCorrectionWorkflow() == CorrectionWorkflow.anonymous) {
 			anonymCorrectionWorkflowEl.select(onKeys[0], true);
 		}
-
+		
+		String[] visibilityValues = new String[] {
+				translate("results.visibility.correction.visible"), translate("results.visibility.correction.not.visible")
+		};
+		resultsVisibilityAfterCorrectionEl = uifactory.addRadiosVertical("results.visibility.correction", "results.visibility.correction", formLayout,
+				visibilityKeys, visibilityValues);
+		if(qti21Module.isResultsVisibleAfterCorrectionWorkflow()) {
+			resultsVisibilityAfterCorrectionEl.select(visibilityKeys[0], true);
+		} else {
+			resultsVisibilityAfterCorrectionEl.select(visibilityKeys[1], true);
+		}
+		
 		mathExtensionEl = uifactory.addCheckboxesHorizontal("math.extension", "math.extension", formLayout,
 				onKeys, onValues);
 		if(qti21Module.isMathAssessExtensionEnabled()) {
@@ -150,6 +164,13 @@ public class QTI21AdminController extends FormBasicController {
 				allOk &= validateCertificatePassword(certificateEl.getInitialFile());
 			}
 		}
+		
+		resultsVisibilityAfterCorrectionEl.clearError();
+		if(!resultsVisibilityAfterCorrectionEl.isOneSelected()) {
+			resultsVisibilityAfterCorrectionEl.setErrorKey("form.legende.mandatory", null);
+			allOk &= false;
+		}
+		
 		return allOk;
 	}
 	
@@ -210,6 +231,7 @@ public class QTI21AdminController extends FormBasicController {
 		CorrectionWorkflow correctionWf = anonymCorrectionWorkflowEl.isAtLeastSelected(1)
 				? CorrectionWorkflow.anonymous : CorrectionWorkflow.named;
 		qti21Module.setCorrectionWorkflow(correctionWf);
+		qti21Module.setResultsVisibleAfterCorrectionWorkflow(resultsVisibilityAfterCorrectionEl.isSelected(0));
 		qti21Module.setMathAssessExtensionEnabled(mathExtensionEl.isSelected(0));
 		qti21Module.setDigitalSignatureEnabled(digitalSignatureEl.isSelected(0));
 		if(digitalSignatureEl.isSelected(0)) {
diff --git a/src/main/java/org/olat/ims/qti21/ui/QTI21AssessmentDetailsController.java b/src/main/java/org/olat/ims/qti21/ui/QTI21AssessmentDetailsController.java
index 0196e8760bb..11f8d715385 100644
--- a/src/main/java/org/olat/ims/qti21/ui/QTI21AssessmentDetailsController.java
+++ b/src/main/java/org/olat/ims/qti21/ui/QTI21AssessmentDetailsController.java
@@ -470,8 +470,12 @@ public class QTI21AssessmentDetailsController extends FormBasicController {
 			passed = Boolean.valueOf(calculated);
 		}
 		AssessmentEntryStatus finalStatus = entryStatus == null ? scoreEval.getAssessmentStatus() : entryStatus;
+		Boolean userVisible = scoreEval.getUserVisible();
+		if(finalStatus == AssessmentEntryStatus.done) {
+			userVisible = Boolean.valueOf(courseNode.isScoreVisibleAfterCorrection());
+		}
 		ScoreEvaluation manualScoreEval = new ScoreEvaluation(score, passed,
-				finalStatus, null, scoreEval.getCurrentRunCompletion(), 
+				finalStatus, userVisible, scoreEval.getCurrentRunCompletion(), 
 				scoreEval.getCurrentRunStatus(), session.getKey());
 		courseAssessmentService.updateScoreEvaluation(courseNode, manualScoreEval, assessedUserCourseEnv,
 				getIdentity(), false, Role.coach);
diff --git a/src/main/java/org/olat/ims/qti21/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/ims/qti21/ui/_i18n/LocalStrings_de.properties
index cacd5293403..2e72dd256b4 100644
--- a/src/main/java/org/olat/ims/qti21/ui/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/ims/qti21/ui/_i18n/LocalStrings_de.properties
@@ -222,6 +222,9 @@ results.title.failed=Sie haben den Test nicht bestanden.
 results.title.for=f\u00FCr {0}
 results.title.generic=Dies sind Ihre Testresultate
 results.title.passed=Sie haben den Test bestanden\!
+results.visibility.correction=Sichbarkeit von Resultaten nach Korrektur
+results.visibility.correction.not.visible=Nicht sichtbar
+results.visibility.correction.visible=Sichtbar
 retrievetest.confirm.text=$org.olat.ims.qti\:retrievetest.confirm.text
 retrievetest.confirm.text.plural=$org.olat.ims.qti.statistics.ui\:retrievetest.confirm.text.plural
 retrievetest.confirm.title=$org.olat.ims.qti.statistics.ui\:retrievetest.confirm.title
diff --git a/src/main/java/org/olat/ims/qti21/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/ims/qti21/ui/_i18n/LocalStrings_en.properties
index 324d588721f..cef4e7c927f 100644
--- a/src/main/java/org/olat/ims/qti21/ui/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/ims/qti21/ui/_i18n/LocalStrings_en.properties
@@ -222,6 +222,9 @@ results.title.failed=Sorry, you have failed the test.
 results.title.for=for {0}
 results.title.generic=This are your test results
 results.title.passed=You have passed the test\!
+results.visibility.correction=Visibility of results after correction
+results.visibility.correction.not.visible=Not visible
+results.visibility.correction.visible=Visible
 retrievetest.confirm.text=$org.olat.ims.qti\:retrievetest.confirm.text
 retrievetest.confirm.text.plural=$org.olat.ims.qti.statistics.ui\:retrievetest.confirm.text.plural
 retrievetest.confirm.title=$org.olat.ims.qti.statistics.ui\:retrievetest.confirm.title
diff --git a/src/main/java/org/olat/modules/grading/ui/GradingAssignmentsListController.java b/src/main/java/org/olat/modules/grading/ui/GradingAssignmentsListController.java
index 2d94bf8b437..fd22d91411d 100644
--- a/src/main/java/org/olat/modules/grading/ui/GradingAssignmentsListController.java
+++ b/src/main/java/org/olat/modules/grading/ui/GradingAssignmentsListController.java
@@ -646,8 +646,12 @@ public class GradingAssignmentsListController extends FormBasicController implem
 				passed = Boolean.valueOf(calculated);
 			}
 			AssessmentEntryStatus finalStatus = status == null ? scoreEval.getAssessmentStatus() : status;
+			Boolean userVisible = scoreEval.getUserVisible();
+			if(finalStatus == AssessmentEntryStatus.done && courseNode instanceof IQTESTCourseNode) {
+				userVisible = Boolean.valueOf(((IQTESTCourseNode)courseNode).isScoreVisibleAfterCorrection());
+			}
 			ScoreEvaluation manualScoreEval = new ScoreEvaluation(score, passed,
-					finalStatus, scoreEval.getUserVisible(), scoreEval.getCurrentRunCompletion(),
+					finalStatus, userVisible, scoreEval.getCurrentRunCompletion(),
 					scoreEval.getCurrentRunStatus(), testSessionsToComplete.getKey());
 			courseAssessmentService.updateScoreEvaluation(courseNode, manualScoreEval, assessedUserCourseEnv,
 					getIdentity(), false, Role.coach);
-- 
GitLab