From 749bcad8dfa62fc79110a7b71638031fe5fc46c4 Mon Sep 17 00:00:00 2001
From: uhensler <urs.hensler@frentix.com>
Date: Fri, 25 Oct 2019 11:51:19 +0200
Subject: [PATCH] OO-4206: Scorm course elements can be fully assessed if
 passed

---
 .../manager/CourseAssessmentManagerImpl.java  |  5 ++
 .../ConditionNodeAccessProvider.java          |  5 ++
 .../learningpath/LearningPathConfigs.java     |  2 +
 .../LearningPathNodeAccessProvider.java       | 10 +++
 .../model/ModuleLearningPathConfigs.java      | 10 +++
 .../model/UnsupportedLearningPathConfigs.java |  5 ++
 .../ui/LearningPathNodeConfigController.java  | 19 +++++
 ...bbableLeaningPathNodeConfigController.java |  2 +-
 .../ui/_i18n/LocalStrings_de.properties       |  2 +
 .../ui/_i18n/LocalStrings_en.properties       |  2 +
 .../course/nodeaccess/NodeAccessProvider.java |  2 +
 .../course/nodeaccess/NodeAccessService.java  | 11 ++-
 .../manager/NodeAccessServiceImpl.java        | 12 ++-
 .../olat/course/nodes/ScormCourseNode.java    | 76 ++++++++++++++-----
 .../nodes/scorm/ScormEditController.java      |  2 +-
 .../scorm/ScormLearningPathNodeHandler.java   |  1 +
 .../nodes/scorm/ScormRunController.java       |  2 +-
 .../scorm/_i18n/LocalStrings_de.properties    |  1 +
 .../scorm/_i18n/LocalStrings_en.properties    |  1 +
 .../st/assessment/STLearningPathConfigs.java  |  5 ++
 .../LearningPathNodeAccessProviderTest.java   | 42 ++++++++++
 21 files changed, 192 insertions(+), 25 deletions(-)

diff --git a/src/main/java/org/olat/course/assessment/manager/CourseAssessmentManagerImpl.java b/src/main/java/org/olat/course/assessment/manager/CourseAssessmentManagerImpl.java
index 68e5b2c83ec..31c2fb4e6fd 100644
--- a/src/main/java/org/olat/course/assessment/manager/CourseAssessmentManagerImpl.java
+++ b/src/main/java/org/olat/course/assessment/manager/CourseAssessmentManagerImpl.java
@@ -528,6 +528,11 @@ public class CourseAssessmentManagerImpl implements AssessmentManager {
 		}
 		assessmentEntry = assessmentService.updateAssessmentEntry(assessmentEntry);
 		DBFactory.getInstance().commit();//commit before sending events
+		
+		if (Boolean.TRUE.equals(passed)) {
+			nodeAccessService.onPassed(courseNode, userCourseEnv , Role.auto);
+		}
+		
 		//reevalute the tree
 		ScoreAccounting scoreAccounting = userCourseEnv.getScoreAccounting();
 		scoreAccounting.evaluateAll(true);
diff --git a/src/main/java/org/olat/course/condition/ConditionNodeAccessProvider.java b/src/main/java/org/olat/course/condition/ConditionNodeAccessProvider.java
index 382432d5ae6..012aff75a2f 100644
--- a/src/main/java/org/olat/course/condition/ConditionNodeAccessProvider.java
+++ b/src/main/java/org/olat/course/condition/ConditionNodeAccessProvider.java
@@ -104,4 +104,9 @@ public class ConditionNodeAccessProvider implements NodeAccessProvider {
 		// nothing to do
 	}
 
+	@Override
+	public void onPassed(CourseNode courseNode, UserCourseEnvironment userCourseEnv, Role by) {
+		// nothing to do
+	}
+
 }
diff --git a/src/main/java/org/olat/course/learningpath/LearningPathConfigs.java b/src/main/java/org/olat/course/learningpath/LearningPathConfigs.java
index 504975bbf73..6332e0fcf1f 100644
--- a/src/main/java/org/olat/course/learningpath/LearningPathConfigs.java
+++ b/src/main/java/org/olat/course/learningpath/LearningPathConfigs.java
@@ -40,6 +40,8 @@ public interface LearningPathConfigs {
 	
 	public FullyAssessedResult isFullyAssessedOnConfirmation();
 
+	public FullyAssessedResult isFullyAssessedOnPassed();
+
 	public FullyAssessedResult isFullyAssessedOnCompletion(Double completion);
 	
 	public FullyAssessedResult isFullyAssessedOnStatus(AssessmentEntryStatus status);
diff --git a/src/main/java/org/olat/course/learningpath/manager/LearningPathNodeAccessProvider.java b/src/main/java/org/olat/course/learningpath/manager/LearningPathNodeAccessProvider.java
index 27adf9dfc61..489ba97ef4e 100644
--- a/src/main/java/org/olat/course/learningpath/manager/LearningPathNodeAccessProvider.java
+++ b/src/main/java/org/olat/course/learningpath/manager/LearningPathNodeAccessProvider.java
@@ -124,6 +124,16 @@ public class LearningPathNodeAccessProvider implements NodeAccessProvider {
 		}
 	}
 
+	@Override
+	public void onPassed(CourseNode courseNode, UserCourseEnvironment userCourseEnv, Role by) {
+		FullyAssessedResult result = getConfigs(courseNode).isFullyAssessedOnPassed();
+		boolean participant = userCourseEnv.isParticipant();
+		if (participant && result.isFullyAssessed()) {
+			AssessmentEntryStatus status = getStatus(courseNode, userCourseEnv, result.isDone());
+			courseAssessmentService.updateFullyAssessed(courseNode, userCourseEnv, Boolean.TRUE, status, by);
+		}
+	}
+
 	@Override
 	public void onCompletionUpdate(CourseNode courseNode, UserCourseEnvironment userCourseEnv,
 			Double completion, AssessmentEntryStatus status, Role by) {
diff --git a/src/main/java/org/olat/course/learningpath/model/ModuleLearningPathConfigs.java b/src/main/java/org/olat/course/learningpath/model/ModuleLearningPathConfigs.java
index 14d7da4c6ba..0b1d769673a 100644
--- a/src/main/java/org/olat/course/learningpath/model/ModuleLearningPathConfigs.java
+++ b/src/main/java/org/olat/course/learningpath/model/ModuleLearningPathConfigs.java
@@ -26,6 +26,7 @@ import static org.olat.course.learningpath.ui.LearningPathNodeConfigController.C
 import static org.olat.course.learningpath.ui.LearningPathNodeConfigController.CONFIG_KEY_TRIGGER;
 import static org.olat.course.learningpath.ui.LearningPathNodeConfigController.CONFIG_VALUE_TRIGGER_CONFIRMED;
 import static org.olat.course.learningpath.ui.LearningPathNodeConfigController.CONFIG_VALUE_TRIGGER_NODE_VISITED;
+import static org.olat.course.learningpath.ui.LearningPathNodeConfigController.CONFIG_VALUE_TRIGGER_PASSED;
 import static org.olat.course.learningpath.ui.LearningPathNodeConfigController.CONFIG_VALUE_TRIGGER_STATUS_DONE;
 
 import org.olat.core.util.StringHelper;
@@ -94,6 +95,15 @@ public class ModuleLearningPathConfigs implements LearningPathConfigs {
 		return LearningPathConfigs.notFullyAssessed();
 	}
 
+	@Override
+	public FullyAssessedResult isFullyAssessedOnPassed() {
+		String doneTriggerName = getDoneTriggerName();
+		if (CONFIG_VALUE_TRIGGER_PASSED.equals(doneTriggerName)) {
+			return LearningPathConfigs.fullyAssessed(true, true);
+		}
+		return LearningPathConfigs.notFullyAssessed();
+	}
+
 	private String getDoneTriggerName() {
 		return moduleConfiguration.getStringValue(CONFIG_KEY_TRIGGER, CONFIG_DEFAULT_TRIGGER);
 	}
diff --git a/src/main/java/org/olat/course/learningpath/model/UnsupportedLearningPathConfigs.java b/src/main/java/org/olat/course/learningpath/model/UnsupportedLearningPathConfigs.java
index d5d4f6a80bb..0e99ad43658 100644
--- a/src/main/java/org/olat/course/learningpath/model/UnsupportedLearningPathConfigs.java
+++ b/src/main/java/org/olat/course/learningpath/model/UnsupportedLearningPathConfigs.java
@@ -56,6 +56,11 @@ public class UnsupportedLearningPathConfigs implements LearningPathConfigs {
 		return LearningPathConfigs.notFullyAssessed();
 	}
 
+	@Override
+	public FullyAssessedResult isFullyAssessedOnPassed() {
+		return LearningPathConfigs.notFullyAssessed();
+	}
+
 	@Override
 	public FullyAssessedResult isFullyAssessedOnCompletion(Double completion) {
 		return LearningPathConfigs.notFullyAssessed();
diff --git a/src/main/java/org/olat/course/learningpath/ui/LearningPathNodeConfigController.java b/src/main/java/org/olat/course/learningpath/ui/LearningPathNodeConfigController.java
index c23edb88740..94bf44c62f7 100644
--- a/src/main/java/org/olat/course/learningpath/ui/LearningPathNodeConfigController.java
+++ b/src/main/java/org/olat/course/learningpath/ui/LearningPathNodeConfigController.java
@@ -58,6 +58,7 @@ public class LearningPathNodeConfigController extends FormBasicController {
 	public static final String CONFIG_VALUE_TRIGGER_NODE_VISITED = "nodeVisited";
 	public static final String CONFIG_VALUE_TRIGGER_CONFIRMED = "confirmed";
 	public static final String CONFIG_VALUE_TRIGGER_STATUS_DONE = "statusDone";
+	public static final String CONFIG_VALUE_TRIGGER_PASSED = "passed";
 	public static final String CONFIG_DEFAULT_TRIGGER = CONFIG_VALUE_TRIGGER_NONE;
 	
 	private TextElement durationEl;
@@ -115,6 +116,9 @@ public class LearningPathNodeConfigController extends FormBasicController {
 		if (ctrlConfig.isTriggerConfirmed()) {
 			triggerKV.add(entry(CONFIG_VALUE_TRIGGER_CONFIRMED, translate("config.trigger.confirmed")));
 		}
+		if (ctrlConfig.isTriggerPassed()) {
+			triggerKV.add(entry(CONFIG_VALUE_TRIGGER_PASSED, translate("config.trigger.passed")));
+		}
 		TranslateableBoolean triggerStatusDone = ctrlConfig.getTriggerStatusDone();
 		if (triggerStatusDone.isTrue()) {
 			triggerKV.add(entry(CONFIG_VALUE_TRIGGER_STATUS_DONE,
@@ -211,6 +215,8 @@ public class LearningPathNodeConfigController extends FormBasicController {
 		
 		public boolean isTriggerConfirmed();
 		
+		public boolean isTriggerPassed();
+		
 		public TranslateableBoolean getTriggerStatusDone();
 		
 	}
@@ -223,6 +229,7 @@ public class LearningPathNodeConfigController extends FormBasicController {
 		
 		private boolean triggerNodeVisited;
 		private boolean triggerConfirmed;
+		private boolean triggerPassed;
 		private TranslateableBoolean triggerStatusDone;
 		
 		private ControllerConfigBuilder() {
@@ -238,6 +245,11 @@ public class LearningPathNodeConfigController extends FormBasicController {
 			return this;
 		}
 		
+		public ControllerConfigBuilder enablePassed() {
+			triggerPassed = true;
+			return this;
+		}
+		
 		public ControllerConfigBuilder enableStatusDone() {
 			triggerStatusDone = TranslateableBoolean.untranslatedTrue();
 			return this;
@@ -256,11 +268,13 @@ public class LearningPathNodeConfigController extends FormBasicController {
 			
 			public final boolean triggerNodeVisited;
 			public final boolean triggerConfirmed;
+			public final boolean triggerPassed;
 			public final TranslateableBoolean triggerStatusDone;
 
 			public ControllerConfigImpl(ControllerConfigBuilder builder) {
 				this.triggerNodeVisited = builder.triggerNodeVisited;
 				this.triggerConfirmed = builder.triggerConfirmed;
+				this.triggerPassed = builder.triggerPassed;
 				this.triggerStatusDone = falseIfNull(builder.triggerStatusDone);
 			}
 			
@@ -280,6 +294,11 @@ public class LearningPathNodeConfigController extends FormBasicController {
 				return triggerConfirmed;
 			}
 
+			@Override
+			public boolean isTriggerPassed() {
+				return triggerPassed;
+			}
+
 			@Override
 			public TranslateableBoolean getTriggerStatusDone() {
 				return triggerStatusDone;
diff --git a/src/main/java/org/olat/course/learningpath/ui/TabbableLeaningPathNodeConfigController.java b/src/main/java/org/olat/course/learningpath/ui/TabbableLeaningPathNodeConfigController.java
index f8b3d2a1eb8..e0da3e1cb8f 100644
--- a/src/main/java/org/olat/course/learningpath/ui/TabbableLeaningPathNodeConfigController.java
+++ b/src/main/java/org/olat/course/learningpath/ui/TabbableLeaningPathNodeConfigController.java
@@ -36,7 +36,7 @@ import org.olat.course.editor.NodeEditController;
  */
 public class TabbableLeaningPathNodeConfigController extends ActivateableTabbableDefaultController {
 
-	private static final String PANE_TAB_LEARNIN_PATH = "pane.tab.learning.path";
+	public static final String PANE_TAB_LEARNIN_PATH = "pane.tab.learning.path";
 	private final static String[] paneKeys = { PANE_TAB_LEARNIN_PATH };
 	
 	private final Controller configCtrl;
diff --git a/src/main/java/org/olat/course/learningpath/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/learningpath/ui/_i18n/LocalStrings_de.properties
index b90d2531300..da1297e8448 100644
--- a/src/main/java/org/olat/course/learningpath/ui/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/course/learningpath/ui/_i18n/LocalStrings_de.properties
@@ -7,8 +7,10 @@ config.obligation.optional=Freiwillig
 config.trigger=Abschluss
 config.trigger.confirmed=Best\u00E4tigt
 config.trigger.none=Ohne Abschluss
+config.trigger.passed=Bestanden
 config.trigger.status.done=Durchf\u00FChrung erledigt
 config.trigger.visited=Kursbaustein ge\u00F6ffnet
+error.fully.assessed.passed=Der Abschluss des Kurselementes kann nicht auf "erledigt" gesetzt sein, wenn im Kursbaustein nicht erledigt werden kann.
 no.configurations=In diesen Kursbaustein stehen keine Konfigurationen zum Lernpfad zu Verf\u00FCgung.
 pane.tab.learning.path=Lernpfad
 reset.all.status=ALLE Stati zur\u00FCcksetzen
diff --git a/src/main/java/org/olat/course/learningpath/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/learningpath/ui/_i18n/LocalStrings_en.properties
index 3dc8c921706..3db4b3b5342 100644
--- a/src/main/java/org/olat/course/learningpath/ui/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/course/learningpath/ui/_i18n/LocalStrings_en.properties
@@ -7,8 +7,10 @@ config.obligation.optional=Optional
 config.trigger=Completion
 config.trigger.confirmed=Confirmed
 config.trigger.none=No completion
+config.trigger.passed=Passed
 config.trigger.status.done=Run done
 config.trigger.visited=Course element visited
+error.fully.assessed.passed=The completion of the course element can't be set to "passed", if the course element can't be passed.
 no.configuration=No configurations for the learning path are available in this course element.
 pane.tab.learning.path=Learning path
 reset.all.status=Reset ALL status
diff --git a/src/main/java/org/olat/course/nodeaccess/NodeAccessProvider.java b/src/main/java/org/olat/course/nodeaccess/NodeAccessProvider.java
index 8d827c56458..e919930b6b3 100644
--- a/src/main/java/org/olat/course/nodeaccess/NodeAccessProvider.java
+++ b/src/main/java/org/olat/course/nodeaccess/NodeAccessProvider.java
@@ -50,6 +50,8 @@ public interface NodeAccessProvider extends NodeAccessProviderIdentifier {
 
 	public void onAssessmentConfirmed(CourseNode courseNode, UserCourseEnvironment userCourseEnv);
 	
+	public void onPassed(CourseNode courseNode, UserCourseEnvironment userCourseEnv, Role by);
+
 	public void onCompletionUpdate(CourseNode courseNode, UserCourseEnvironment userCourseEnv,
 			Double completion, AssessmentEntryStatus status, Role by);
 
diff --git a/src/main/java/org/olat/course/nodeaccess/NodeAccessService.java b/src/main/java/org/olat/course/nodeaccess/NodeAccessService.java
index 97d0d563e24..60fc86180c3 100644
--- a/src/main/java/org/olat/course/nodeaccess/NodeAccessService.java
+++ b/src/main/java/org/olat/course/nodeaccess/NodeAccessService.java
@@ -69,7 +69,7 @@ public interface NodeAccessService {
 	public CourseTreeModelBuilder getCourseTreeModelBuilder(UserCourseEnvironment userCourseEnv);
 	
 	/**
-	 * Returns if a user can confirm the execution of a assessemnt
+	 * Returns if a user can confirm the execution of an assessment.
 	 *
 	 * @param courseNode
 	 * @param userCourseEnv
@@ -85,6 +85,15 @@ public interface NodeAccessService {
 	 */
 	public void onAssessmentConfirmed(CourseNode courseNode, UserCourseEnvironment userCourseEnv);
 
+	/**
+	 * Hook after the user has passed an assessment.
+	 *
+	 * @param courseNode
+	 * @param userCourseEnv
+	 * @param by
+	 */
+	public void onPassed(CourseNode courseNode, UserCourseEnvironment userCourseEnv, Role by);
+
 	/**
 	 * Hook after the completion and the run status is updated.
 	 *
diff --git a/src/main/java/org/olat/course/nodeaccess/manager/NodeAccessServiceImpl.java b/src/main/java/org/olat/course/nodeaccess/manager/NodeAccessServiceImpl.java
index 5ee6111040d..20840c09295 100644
--- a/src/main/java/org/olat/course/nodeaccess/manager/NodeAccessServiceImpl.java
+++ b/src/main/java/org/olat/course/nodeaccess/manager/NodeAccessServiceImpl.java
@@ -103,6 +103,12 @@ public class NodeAccessServiceImpl implements NodeAccessService, NodeVisitedList
 		getNodeAccessProvider(type).onAssessmentConfirmed(courseNode, userCourseEnv);
 	}
 
+	@Override
+	public void onPassed(CourseNode courseNode, UserCourseEnvironment userCourseEnv, Role by) {
+		NodeAccessType type = NodeAccessType.of(userCourseEnv);
+		getNodeAccessProvider(type).onPassed(courseNode, userCourseEnv, by);
+	}
+
 	@Override
 	public void onCompletionUpdate(CourseNode courseNode, UserCourseEnvironment userCourseEnv,
 			Double completion, AssessmentEntryStatus status, Role by) {
@@ -111,9 +117,9 @@ public class NodeAccessServiceImpl implements NodeAccessService, NodeVisitedList
 	}
 
 	@Override
-	public boolean onNodeVisited(CourseNode courseNode, UserCourseEnvironment userCourseEnvironment) {
-		NodeAccessType type = NodeAccessType.of(userCourseEnvironment);
-		return getNodeAccessProvider(type).onNodeVisited(courseNode, userCourseEnvironment);
+	public boolean onNodeVisited(CourseNode courseNode, UserCourseEnvironment userCourseEnv) {
+		NodeAccessType type = NodeAccessType.of(userCourseEnv);
+		return getNodeAccessProvider(type).onNodeVisited(courseNode, userCourseEnv);
 	}
 
 }
diff --git a/src/main/java/org/olat/course/nodes/ScormCourseNode.java b/src/main/java/org/olat/course/nodes/ScormCourseNode.java
index 9cbc5029ca0..7f47c159ff1 100644
--- a/src/main/java/org/olat/course/nodes/ScormCourseNode.java
+++ b/src/main/java/org/olat/course/nodes/ScormCourseNode.java
@@ -27,6 +27,7 @@ package org.olat.course.nodes;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 import java.util.Locale;
@@ -55,8 +56,11 @@ import org.olat.course.editor.ConditionAccessEditConfig;
 import org.olat.course.editor.CourseEditorEnv;
 import org.olat.course.editor.NodeEditController;
 import org.olat.course.editor.StatusDescription;
+import org.olat.course.learningpath.ui.TabbableLeaningPathNodeConfigController;
 import org.olat.course.nodes.cp.CPEditController;
+import org.olat.course.nodes.scorm.ScormAssessmentConfig;
 import org.olat.course.nodes.scorm.ScormEditController;
+import org.olat.course.nodes.scorm.ScormLearningPathNodeHandler;
 import org.olat.course.nodes.scorm.ScormRunController;
 import org.olat.course.properties.CoursePropertyManager;
 import org.olat.course.run.navigation.NodeRunConstructionResult;
@@ -81,6 +85,9 @@ public class ScormCourseNode extends AbstractAccessableCourseNode {
 	
 	private static final Logger log = Tracing.createLoggerFor(ScormCourseNode.class);
 	private static final long serialVersionUID = 2970594874787761801L;
+	@SuppressWarnings("deprecation")
+	private static final String TRANSLATOR_PACKAGE = Util.getPackageName(ScormEditController.class);
+	
 	public static final String TYPE = "scorm";
 	private static final int CURRENT_CONFIG_VERSION = 5;
 	
@@ -125,33 +132,66 @@ public class ScormCourseNode extends AbstractAccessableCourseNode {
 
 	@Override
 	public StatusDescription isConfigValid() {
-		if (oneClickStatusCache != null) { return oneClickStatusCache[0]; }
-
-		StatusDescription sd = StatusDescription.NOERROR;
-		boolean isValid = ScormEditController.isModuleConfigValid(getModuleConfiguration());
-		if (!isValid) {
-			String shortKey = "error.noreference.short";
-			String longKey = "error.noreference.long";
-			String[] params = new String[] { this.getShortTitle() };
-			String translPackage = Util.getPackageName(ScormEditController.class);
-			sd = new StatusDescription(StatusDescription.ERROR, shortKey, longKey, params, translPackage);
-			sd.setDescriptionForUnit(getIdent());
-			// set which pane is affected by error
-			sd.setActivateableViewIdentifier(ScormEditController.PANE_TAB_CPCONFIG);
+		if (oneClickStatusCache != null && oneClickStatusCache.length > 0) {
+			return oneClickStatusCache[0];
 		}
-		return sd;
+		
+		List<StatusDescription> statusDescs = validateInternalConfiguration();
+		if(statusDescs.isEmpty()) {
+			statusDescs.add(StatusDescription.NOERROR);
+		}
+		oneClickStatusCache = StatusDescriptionHelper.sort(statusDescs);
+		return oneClickStatusCache[0];
 	}
 
 	@Override
 	public StatusDescription[] isConfigValid(CourseEditorEnv cev) {
 		oneClickStatusCache = null;
-		// only here we know which translator to take for translating condition
-		// error messages
-		String translatorStr = Util.getPackageName(ScormEditController.class);
-		List<StatusDescription> sds = isConfigValidWithTranslator(cev, translatorStr, getConditionExpressions());
+		
+		List<StatusDescription> sds = isConfigValidWithTranslator(cev, TRANSLATOR_PACKAGE, getConditionExpressions());
+		if(oneClickStatusCache != null && oneClickStatusCache.length > 0) {
+			//isConfigValidWithTranslator add first
+			sds.remove(oneClickStatusCache[0]);
+		}
+		sds.addAll(validateInternalConfiguration());
 		oneClickStatusCache = StatusDescriptionHelper.sort(sds);
 		return oneClickStatusCache;
 	}
+	
+	private List<StatusDescription> validateInternalConfiguration() {
+		List<StatusDescription> sdList = new ArrayList<>(2);
+
+		boolean hasScormReference = ScormEditController.hasScormReference(getModuleConfiguration());
+		if (!hasScormReference) {
+			addStatusErrorDescription("error.noreference.short", "error.noreference.long", ScormEditController.PANE_TAB_CPCONFIG, sdList);
+		}
+		
+		if (isFullyAssessedPassedConfigError()) {
+			addStatusErrorDescription("error.fully.assessed.passed", "error.fully.assessed.passed",
+					TabbableLeaningPathNodeConfigController.PANE_TAB_LEARNIN_PATH, sdList);
+		}
+		
+		return sdList;
+	}
+	
+	private boolean isFullyAssessedPassedConfigError() {
+		boolean hasPassed = new ScormAssessmentConfig(getModuleConfiguration()).hasPassed();
+		boolean isPassedTrigger = CoreSpringFactory.getImpl(ScormLearningPathNodeHandler.class)
+				.getConfigs(this)
+				.isFullyAssessedOnPassed()
+				.isFullyAssessed();
+		return isPassedTrigger && !hasPassed;
+	}
+
+	private void addStatusErrorDescription(String shortDescKey, String longDescKey, String pane,
+			List<StatusDescription> status) {
+		String[] params = new String[] { getShortTitle() };
+		StatusDescription sd = new StatusDescription(StatusDescription.ERROR, shortDescKey, longDescKey, params,
+				TRANSLATOR_PACKAGE);
+		sd.setDescriptionForUnit(getIdent());
+		sd.setActivateableViewIdentifier(pane);
+		status.add(sd);
+	}
 
 	@Override
 	public RepositoryEntry getReferencedRepositoryEntry() {
diff --git a/src/main/java/org/olat/course/nodes/scorm/ScormEditController.java b/src/main/java/org/olat/course/nodes/scorm/ScormEditController.java
index 661a5e522c0..36375989b9f 100644
--- a/src/main/java/org/olat/course/nodes/scorm/ScormEditController.java
+++ b/src/main/java/org/olat/course/nodes/scorm/ScormEditController.java
@@ -357,7 +357,7 @@ public class ScormEditController extends ActivateableTabbableDefaultController i
 		moduleConfiguration.set(CONFIG_KEY_REPOSITORY_SOFTKEY, re.getSoftkey());
 	}
 
-	public static boolean isModuleConfigValid(ModuleConfiguration moduleConfiguration) {
+	public static boolean hasScormReference(ModuleConfiguration moduleConfiguration) {
 		return (moduleConfiguration.get(CONFIG_KEY_REPOSITORY_SOFTKEY) != null);
 	}
 
diff --git a/src/main/java/org/olat/course/nodes/scorm/ScormLearningPathNodeHandler.java b/src/main/java/org/olat/course/nodes/scorm/ScormLearningPathNodeHandler.java
index 9d6cca8035c..dba10904fa8 100644
--- a/src/main/java/org/olat/course/nodes/scorm/ScormLearningPathNodeHandler.java
+++ b/src/main/java/org/olat/course/nodes/scorm/ScormLearningPathNodeHandler.java
@@ -62,6 +62,7 @@ public class ScormLearningPathNodeHandler implements LearningPathNodeHandler {
 		LearningPathControllerConfig ctrlConfig = LearningPathNodeConfigController.builder()
 				.enableNodeVisited()
 				.enableConfirmed()
+				.enablePassed()
 				.build();
 		return new LearningPathNodeConfigController(ureq, wControl, courseEntry, courseNode.getModuleConfiguration(), ctrlConfig);
 	}
diff --git a/src/main/java/org/olat/course/nodes/scorm/ScormRunController.java b/src/main/java/org/olat/course/nodes/scorm/ScormRunController.java
index 4d3d8083e3e..798d4c0f39b 100644
--- a/src/main/java/org/olat/course/nodes/scorm/ScormRunController.java
+++ b/src/main/java/org/olat/course/nodes/scorm/ScormRunController.java
@@ -124,7 +124,7 @@ public class ScormRunController extends BasicController implements ScormAPICallb
 			ScormCourseNode scormNode, boolean isPreview) {
 		super(ureq, wControl, Util.createPackageTranslator(CourseNode.class, ureq.getLocale()));
 		// assertion to make sure the moduleconfig is valid
-		if (!ScormEditController.isModuleConfigValid(config))
+		if (!ScormEditController.hasScormReference(config))
 			throw new AssertException("scorm run controller had an invalid module config:" + config.toString());
 		this.isPreview = isPreview || userCourseEnv.isCourseReadOnly() || !userCourseEnv.isParticipant();
 		this.userCourseEnv = userCourseEnv;
diff --git a/src/main/java/org/olat/course/nodes/scorm/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/nodes/scorm/_i18n/LocalStrings_de.properties
index e8a175025db..6848b586b14 100644
--- a/src/main/java/org/olat/course/nodes/scorm/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/course/nodes/scorm/_i18n/LocalStrings_de.properties
@@ -37,6 +37,7 @@ cutvalue.label=Notwendige Punktzahl f\u00FCr 'bestanden'
 cutvalue.validation=Geben Sie eine Ganzzahl ein
 error.cprepoentrymissing=Der SCORM-Lerninhalt, den Sie anzeigen m\u00F6chten, wurde in der Zwischenzeit in der Ablage der Lernressourcen gel\u00F6scht
 error.cprepoentrymissing.user=Der SCORM-Lerninhalt den Sie anzeigen m\u00F6chten existiert nicht mehr. Bitte kontaktieren Sie ihren Kursbetreuer.
+error.fully.assessed.passed=$org.olat.course.learningpath.ui:\error.fully.assessed.passed
 error.launch=SCORM-Lerninhalt konnte nicht gestartet werden.
 error.noreference.long=F\u00FCr "{0}" muss in der Konfiguration ein SCORM-Lerninhalt im Tab "Lerninhalt" ausgew\u00E4hlt werden.
 error.noreference.short=Es ist noch kein SCORM-Lerninhalt f\u00FCr "{0}" ausgew\u00E4hlt.
diff --git a/src/main/java/org/olat/course/nodes/scorm/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/nodes/scorm/_i18n/LocalStrings_en.properties
index 8f0281ac344..3c637938bf7 100644
--- a/src/main/java/org/olat/course/nodes/scorm/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/course/nodes/scorm/_i18n/LocalStrings_en.properties
@@ -43,6 +43,7 @@ cutvalue.label=Score needed to pass
 cutvalue.validation=Please enter an integer
 error.cprepoentrymissing=This SCORM learning content was deleted in the meantime within the storage folder of learning resources.
 error.cprepoentrymissing.user=The SCORM learning content you were trying to access doesn't exist anymore. Please contact your course coach.
+error.fully.assessed.passed=$org.olat.course.learningpath.ui:\error.fully.assessed.passed
 error.launch=Unable to start SCORM learning content.
 error.noreference.long=For "{0}" you have to select a SCORM learning content in the tab "Learning content" within the configuration section.
 error.noreference.short=No SCORM learning content selected for "{0}" yet.
diff --git a/src/main/java/org/olat/course/nodes/st/assessment/STLearningPathConfigs.java b/src/main/java/org/olat/course/nodes/st/assessment/STLearningPathConfigs.java
index 37e3c563653..9e3323b9050 100644
--- a/src/main/java/org/olat/course/nodes/st/assessment/STLearningPathConfigs.java
+++ b/src/main/java/org/olat/course/nodes/st/assessment/STLearningPathConfigs.java
@@ -56,6 +56,11 @@ public class STLearningPathConfigs implements LearningPathConfigs {
 		return LearningPathConfigs.notFullyAssessed();
 	}
 
+	@Override
+	public FullyAssessedResult isFullyAssessedOnPassed() {
+		return LearningPathConfigs.notFullyAssessed();
+	}
+
 	@Override
 	public FullyAssessedResult isFullyAssessedOnStatus(AssessmentEntryStatus status) {
 		return LearningPathConfigs.notFullyAssessed();
diff --git a/src/test/java/org/olat/course/learningpath/manager/LearningPathNodeAccessProviderTest.java b/src/test/java/org/olat/course/learningpath/manager/LearningPathNodeAccessProviderTest.java
index d84204daa78..913e25ff750 100644
--- a/src/test/java/org/olat/course/learningpath/manager/LearningPathNodeAccessProviderTest.java
+++ b/src/test/java/org/olat/course/learningpath/manager/LearningPathNodeAccessProviderTest.java
@@ -197,6 +197,48 @@ public class LearningPathNodeAccessProviderTest {
 				AssessmentEntryStatus.done, Role.user);
 	}
 	
+	@Test
+	public void shouldSetAssessmentAsDoneIfPassed() {
+		LearningPathConfigs configs = mock(LearningPathConfigs.class);
+		when(configs.isFullyAssessedOnPassed()).thenReturn(fullyAssessed(true, true));
+		LearningPathNodeHandler handler = mock(LearningPathNodeHandler.class);
+		when(handler.getConfigs(courseNodeMock)).thenReturn(configs);
+		when(registry.getLearningPathNodeHandler(courseNodeMock)).thenReturn(handler);
+
+		sut.onPassed(courseNodeMock, participantCourseEnv, Role.user);
+
+		verify(courseAssessmentService).updateFullyAssessed(courseNodeMock, participantCourseEnv, Boolean.TRUE,
+				AssessmentEntryStatus.done, Role.user);
+	}
+	
+	@Test
+	public void shouldSetAssessmentAsDoneIfPassedNotEnabled() {
+		LearningPathConfigs configs = mock(LearningPathConfigs.class);
+		when(configs.isFullyAssessedOnPassed()).thenReturn(notFullyAssessed());
+		LearningPathNodeHandler handler = mock(LearningPathNodeHandler.class);
+		when(handler.getConfigs(courseNodeMock)).thenReturn(configs);
+		when(registry.getLearningPathNodeHandler(courseNodeMock)).thenReturn(handler);
+
+		sut.onPassed(courseNodeMock, participantCourseEnv, Role.user);
+
+		verify(courseAssessmentService, never()).updateFullyAssessed(courseNodeMock, participantCourseEnv, Boolean.TRUE,
+				AssessmentEntryStatus.done, Role.user);
+	}
+	
+	@Test
+	public void shouldNotChangeAssessmentIfNotAParticipantPassed() {
+		LearningPathConfigs configs = mock(LearningPathConfigs.class);
+		when(configs.isFullyAssessedOnPassed()).thenReturn(fullyAssessed(true, true));
+		LearningPathNodeHandler handler = mock(LearningPathNodeHandler.class);
+		when(handler.getConfigs(courseNodeMock)).thenReturn(configs);
+		when(registry.getLearningPathNodeHandler(courseNodeMock)).thenReturn(handler);
+
+		sut.onPassed(courseNodeMock, coachCourseEnv, Role.user);
+
+		verify(courseAssessmentService, never()).updateFullyAssessed(courseNodeMock, participantCourseEnv, Boolean.TRUE,
+				AssessmentEntryStatus.done, Role.user);
+	}
+	
 	@Test
 	public void shouldSetAssessmentAsDoneIfRunStatusIsReached() {
 		Role role = Role.auto;
-- 
GitLab