From 3fc4f714494cc2d987cd0fd7de19730b8c85a057 Mon Sep 17 00:00:00 2001
From: uhensler <urs.hensler@frentix.com>
Date: Wed, 23 Oct 2019 09:15:52 +0200
Subject: [PATCH] OO-4207: Structure nodes are done if all mandatory children
 are done

Evaluation of the obligation of the structure node
---
 .../manager/CourseAssessmentManagerImpl.java  |  1 +
 .../evaluation/ConfigObligationEvaluator.java | 11 ++-
 .../DefaultLinearStatusEvaluator.java         | 14 +++-
 .../MandatoryObligationEvaluator.java         | 53 ++++++++++++
 .../st/assessment/STAssessmentHandler.java    |  7 +-
 .../assessment/STLinearStatusEvaluator.java   | 34 +++++++-
 .../st/assessment/ScoreStatusEvaluator.java   |  2 +-
 .../scoring/AccountingEvaluatorsFactory.java  | 10 ++-
 .../run/scoring/AssessmentAccounting.java     | 20 +++--
 .../run/scoring/ObligationEvaluator.java      |  6 +-
 .../course/run/scoring/StatusEvaluator.java   |  2 +-
 .../DefaultLinearStatusEvaluatorTest.java     | 21 +++--
 .../MandatoryObligationEvaluatorTest.java     | 82 +++++++++++++++++++
 .../STLinearStatusEvaluatorTest.java          | 55 ++++++++++---
 .../java/org/olat/test/AllTestsJunit4.java    |  1 +
 15 files changed, 278 insertions(+), 41 deletions(-)
 create mode 100644 src/main/java/org/olat/course/nodes/st/assessment/MandatoryObligationEvaluator.java
 create mode 100644 src/test/java/org/olat/course/nodes/st/assessment/MandatoryObligationEvaluatorTest.java

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 a56fbf8817d..68e5b2c83ec 100644
--- a/src/main/java/org/olat/course/assessment/manager/CourseAssessmentManagerImpl.java
+++ b/src/main/java/org/olat/course/assessment/manager/CourseAssessmentManagerImpl.java
@@ -139,6 +139,7 @@ public class CourseAssessmentManagerImpl implements AssessmentManager {
 
 	@Override
 	public List<AssessmentEntry> getAssessmentEntries(Identity assessedIdentity) {
+		log.debug("Load assessment entries of {}", assessedIdentity);
 		return assessmentService.loadAssessmentEntriesByAssessedIdentity(assessedIdentity, cgm.getCourseEntry());
 	}
 
diff --git a/src/main/java/org/olat/course/learningpath/evaluation/ConfigObligationEvaluator.java b/src/main/java/org/olat/course/learningpath/evaluation/ConfigObligationEvaluator.java
index 11cd7dfc3bf..051129d6650 100644
--- a/src/main/java/org/olat/course/learningpath/evaluation/ConfigObligationEvaluator.java
+++ b/src/main/java/org/olat/course/learningpath/evaluation/ConfigObligationEvaluator.java
@@ -19,9 +19,12 @@
  */
 package org.olat.course.learningpath.evaluation;
 
+import java.util.List;
+
 import org.olat.core.CoreSpringFactory;
 import org.olat.course.learningpath.LearningPathService;
 import org.olat.course.nodes.CourseNode;
+import org.olat.course.run.scoring.AssessmentEvaluation;
 import org.olat.course.run.scoring.ObligationEvaluator;
 import org.olat.modules.assessment.model.AssessmentObligation;
 
@@ -34,9 +37,15 @@ import org.olat.modules.assessment.model.AssessmentObligation;
 public class ConfigObligationEvaluator implements ObligationEvaluator {
 
 	@Override
-	public AssessmentObligation getObligation(CourseNode courseNode) {
+	public AssessmentObligation getObligation(AssessmentEvaluation currentEvaluation, CourseNode courseNode) {
 		LearningPathService learningPathService = CoreSpringFactory.getImpl(LearningPathService.class);
 		return learningPathService.getConfigs(courseNode).getObligation();
 	}
 
+	@Override
+	public AssessmentObligation getObligation(AssessmentEvaluation currentEvaluation,
+			List<AssessmentEvaluation> children) {
+		return currentEvaluation.getObligation();
+	}
+
 }
diff --git a/src/main/java/org/olat/course/learningpath/evaluation/DefaultLinearStatusEvaluator.java b/src/main/java/org/olat/course/learningpath/evaluation/DefaultLinearStatusEvaluator.java
index 09b15b21e43..e6925ce4a6a 100644
--- a/src/main/java/org/olat/course/learningpath/evaluation/DefaultLinearStatusEvaluator.java
+++ b/src/main/java/org/olat/course/learningpath/evaluation/DefaultLinearStatusEvaluator.java
@@ -39,11 +39,12 @@ public class DefaultLinearStatusEvaluator implements StatusEvaluator {
 	private static final Logger log = Tracing.createLoggerFor(DefaultLinearStatusEvaluator.class);
 
 	@Override
-	public AssessmentEntryStatus getStatus(AssessmentEvaluation previousEvaluation, AssessmentEvaluation currentEvaluation) {
+	public AssessmentEntryStatus getStatus(AssessmentEvaluation previousEvaluation,
+			AssessmentEvaluation currentEvaluation, boolean firstChild) {
 		AssessmentEntryStatus currentStatus = currentEvaluation.getAssessmentStatus();
 		AssessmentEntryStatus status = currentStatus;
 		if (isNotReadyYet(currentStatus)) {
-			if (isAccessible(previousEvaluation)) {
+			if (isAccessible(previousEvaluation, firstChild)) {
 				status = AssessmentEntryStatus.notStarted;
 			} else {
 				status = AssessmentEntryStatus.notReady;
@@ -67,10 +68,11 @@ public class DefaultLinearStatusEvaluator implements StatusEvaluator {
 		return currentStatus == null || AssessmentEntryStatus.notReady.equals(currentStatus);
 	}
 	
-	private boolean isAccessible(AssessmentEvaluation assessmentEvaluation) {
+	private boolean isAccessible(AssessmentEvaluation assessmentEvaluation, boolean firstChild) {
 		return isRoot(assessmentEvaluation)
 				|| isFullyAssessed(assessmentEvaluation)
-				|| isOptionalAndReady(assessmentEvaluation);
+				|| isOptionalAndReady(assessmentEvaluation)
+				|| isFirstChildAndNotStarted(assessmentEvaluation, firstChild);
 	}
 
 	private boolean isRoot(AssessmentEvaluation assessmentEvaluation) {
@@ -86,6 +88,10 @@ public class DefaultLinearStatusEvaluator implements StatusEvaluator {
 				&& !AssessmentEntryStatus.notReady.equals(assessmentEvaluation.getAssessmentStatus());
 	}
 
+	private boolean isFirstChildAndNotStarted(AssessmentEvaluation assessmentEvaluation, boolean firstChild) {
+		return firstChild && AssessmentEntryStatus.notStarted.equals(assessmentEvaluation.getAssessmentStatus());
+	}
+
 	@Override
 	public AssessmentEntryStatus getStatus(AssessmentEvaluation currentEvaluation,
 			List<AssessmentEvaluation> children) {
diff --git a/src/main/java/org/olat/course/nodes/st/assessment/MandatoryObligationEvaluator.java b/src/main/java/org/olat/course/nodes/st/assessment/MandatoryObligationEvaluator.java
new file mode 100644
index 00000000000..db1a2b48d21
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/st/assessment/MandatoryObligationEvaluator.java
@@ -0,0 +1,53 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.course.nodes.st.assessment;
+
+import java.util.List;
+
+import org.olat.course.nodes.CourseNode;
+import org.olat.course.run.scoring.AssessmentEvaluation;
+import org.olat.course.run.scoring.ObligationEvaluator;
+import org.olat.modules.assessment.model.AssessmentObligation;
+
+/**
+ * 
+ * Initial date: 23 Oct 2019<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+public class MandatoryObligationEvaluator implements ObligationEvaluator {
+
+	@Override
+	public AssessmentObligation getObligation(AssessmentEvaluation currentEvaluation, CourseNode courseNode) {
+		return currentEvaluation.getObligation();
+	}
+
+	@Override
+	public AssessmentObligation getObligation(AssessmentEvaluation currentEvaluation,
+			List<AssessmentEvaluation> children) {
+		for (AssessmentEvaluation child : children) {
+			if (AssessmentObligation.mandatory.equals(child.getObligation())) {
+				return AssessmentObligation.mandatory;
+			}
+		}
+		return AssessmentObligation.optional;
+	}
+
+}
diff --git a/src/main/java/org/olat/course/nodes/st/assessment/STAssessmentHandler.java b/src/main/java/org/olat/course/nodes/st/assessment/STAssessmentHandler.java
index 74dade79b6d..bad65e678a1 100644
--- a/src/main/java/org/olat/course/nodes/st/assessment/STAssessmentHandler.java
+++ b/src/main/java/org/olat/course/nodes/st/assessment/STAssessmentHandler.java
@@ -47,6 +47,7 @@ import org.olat.course.run.scoring.AverageCompletionEvaluator;
 import org.olat.course.run.scoring.CompletionEvaluator;
 import org.olat.course.run.scoring.FullyAssessedEvaluator;
 import org.olat.course.run.scoring.LastModificationsEvaluator;
+import org.olat.course.run.scoring.ObligationEvaluator;
 import org.olat.course.run.scoring.PassedEvaluator;
 import org.olat.course.run.scoring.ScoreCalculator;
 import org.olat.course.run.scoring.ScoreEvaluator;
@@ -68,11 +69,12 @@ import org.springframework.stereotype.Service;
 @Service
 public class STAssessmentHandler implements AssessmentHandler {
 	
+	private static final ObligationEvaluator MANDATORY_OBLIGATION_EVALUATOR = new MandatoryObligationEvaluator();
 	private static final CumulatingDurationEvaluator CUMULATION_DURATION_EVALUATOR = new CumulatingDurationEvaluator();
 	private static final ScoreEvaluator CONDITION_SCORE_EVALUATOR = new ConditionScoreEvaluator();
 	private static final PassedEvaluator CONDITION_PASSED_EVALUATOR = new ConditionPassedEvaluator();
 	private static final StatusEvaluator SCORE_STATUS_EVALUATOR = new ScoreStatusEvaluator();
-	private static final StatusEvaluator STATUS_LEARNING_PATH_STATUS_EVALUATOR = new STLinearStatusEvaluator();
+	private static final StatusEvaluator LEARNING_PATH_STATUS_EVALUATOR = new STLinearStatusEvaluator();
 	private static final FullyAssessedEvaluator FULLY_ASSESSED_EVALUATOR = new STFullyAssessedEvaluator();
 	private static final LastModificationsEvaluator LAST_MODIFICATION_EVALUATOR = new STLastModificationsEvaluator();
 	private static final AccountingEvaluators CONVENTIONAL_EVALUATORS = AccountingEvaluatorsBuilder.builder()
@@ -107,10 +109,11 @@ public class STAssessmentHandler implements AssessmentHandler {
 	public AccountingEvaluators getEvaluators(CourseNode courseNode, CourseConfig courseConfig) {
 		if (LearningPathNodeAccessProvider.TYPE.equals(courseConfig.getNodeAccessType().getType())) {
 			AccountingEvaluatorsBuilder builder = AccountingEvaluatorsBuilder.builder()
+					.withObligationEvaluator(MANDATORY_OBLIGATION_EVALUATOR)
 					.withDurationEvaluator(CUMULATION_DURATION_EVALUATOR)
 					.withScoreEvaluator(CONDITION_SCORE_EVALUATOR)
 					.withPassedEvaluator(CONDITION_PASSED_EVALUATOR)
-					.withStatusEvaluator(STATUS_LEARNING_PATH_STATUS_EVALUATOR)
+					.withStatusEvaluator(LEARNING_PATH_STATUS_EVALUATOR)
 					.withFullyAssessedEvaluator(FULLY_ASSESSED_EVALUATOR)
 					.withLastModificationsEvaluator(LAST_MODIFICATION_EVALUATOR);
 			CompletionEvaluator completionEvaluator = CompletionType.duration.equals(courseConfig.getCompletionType())
diff --git a/src/main/java/org/olat/course/nodes/st/assessment/STLinearStatusEvaluator.java b/src/main/java/org/olat/course/nodes/st/assessment/STLinearStatusEvaluator.java
index 93a8dc550e7..dbbb765c1f6 100644
--- a/src/main/java/org/olat/course/nodes/st/assessment/STLinearStatusEvaluator.java
+++ b/src/main/java/org/olat/course/nodes/st/assessment/STLinearStatusEvaluator.java
@@ -25,6 +25,7 @@ import org.olat.course.learningpath.evaluation.DefaultLinearStatusEvaluator;
 import org.olat.course.run.scoring.AssessmentEvaluation;
 import org.olat.course.run.scoring.StatusEvaluator;
 import org.olat.modules.assessment.model.AssessmentEntryStatus;
+import org.olat.modules.assessment.model.AssessmentObligation;
 
 /**
  * 
@@ -38,25 +39,50 @@ public class STLinearStatusEvaluator implements StatusEvaluator {
 
 	@Override
 	public AssessmentEntryStatus getStatus(AssessmentEvaluation previousEvaluation,
-			AssessmentEvaluation currentEvaluation) {
-		return defaultEvaluator.getStatus(previousEvaluation, currentEvaluation);
+			AssessmentEvaluation currentEvaluation, boolean firstChild) {
+		return defaultEvaluator.getStatus(previousEvaluation, currentEvaluation, firstChild);
 	}
 
 	@Override
 	public AssessmentEntryStatus getStatus(AssessmentEvaluation currentEvaluation,
 			List<AssessmentEvaluation> children) {
+		boolean notStarted = false;
+		boolean inProgress = false;
+		boolean done = true;
 		for (AssessmentEvaluation child : children) {
+			if (isNotStarted(child)) {
+				notStarted = true;
+			}
 			if (isInProgess(child)) {
-				return AssessmentEntryStatus.inProgress;
+				inProgress = true;
+			}
+			if (isMandatory(child) && isNotDone(child)) {
+				done = false;
 			}
 		}
-		return currentEvaluation.getAssessmentStatus();
+		
+		if (done)        return AssessmentEntryStatus.done;
+		if (inProgress)  return AssessmentEntryStatus.inProgress;
+		if (notStarted)  return AssessmentEntryStatus.notStarted;
+		                 return currentEvaluation.getAssessmentStatus();
 	}
 	
+	private boolean isNotStarted(AssessmentEvaluation assessmentEvaluation) {
+		return isInProgess(assessmentEvaluation)
+				|| AssessmentEntryStatus.notStarted.equals(assessmentEvaluation.getAssessmentStatus());
+	}
+
 	private boolean isInProgess(AssessmentEvaluation assessmentEvaluation) {
 		return AssessmentEntryStatus.inProgress.equals(assessmentEvaluation.getAssessmentStatus())
 				|| AssessmentEntryStatus.inReview.equals(assessmentEvaluation.getAssessmentStatus())
 				|| AssessmentEntryStatus.done.equals(assessmentEvaluation.getAssessmentStatus());
 	}
 
+	private boolean isMandatory(AssessmentEvaluation evaluation) {
+		return evaluation.getObligation() != null && AssessmentObligation.mandatory.equals(evaluation.getObligation());
+	}
+	private boolean isNotDone(AssessmentEvaluation assessmentEvaluation) {
+		return !AssessmentEntryStatus.done.equals(assessmentEvaluation.getAssessmentStatus());
+	}
+
 }
diff --git a/src/main/java/org/olat/course/nodes/st/assessment/ScoreStatusEvaluator.java b/src/main/java/org/olat/course/nodes/st/assessment/ScoreStatusEvaluator.java
index 3ce8806f298..ca679c440b7 100644
--- a/src/main/java/org/olat/course/nodes/st/assessment/ScoreStatusEvaluator.java
+++ b/src/main/java/org/olat/course/nodes/st/assessment/ScoreStatusEvaluator.java
@@ -35,7 +35,7 @@ public class ScoreStatusEvaluator implements StatusEvaluator {
 
 	@Override
 	public AssessmentEntryStatus getStatus(AssessmentEvaluation previousEvaluation,
-			AssessmentEvaluation currentEvaluation) {
+			AssessmentEvaluation currentEvaluation, boolean firstChild) {
 		if (currentEvaluation.getPassed() != null && currentEvaluation.getPassed().booleanValue()) {
 			return AssessmentEntryStatus.done;
 		} else if (currentEvaluation.getScore() != null) {
diff --git a/src/main/java/org/olat/course/run/scoring/AccountingEvaluatorsFactory.java b/src/main/java/org/olat/course/run/scoring/AccountingEvaluatorsFactory.java
index 3520fb46490..20840356838 100644
--- a/src/main/java/org/olat/course/run/scoring/AccountingEvaluatorsFactory.java
+++ b/src/main/java/org/olat/course/run/scoring/AccountingEvaluatorsFactory.java
@@ -83,7 +83,13 @@ class AccountingEvaluatorsFactory {
 	private static class NullObligationEvaluator implements ObligationEvaluator {
 
 		@Override
-		public AssessmentObligation getObligation(CourseNode courseNode) {
+		public AssessmentObligation getObligation(AssessmentEvaluation currentEvaluation, CourseNode courseNode) {
+			return null;
+		}
+
+		@Override
+		public AssessmentObligation getObligation(AssessmentEvaluation currentEvaluation,
+				List<AssessmentEvaluation> children) {
 			return null;
 		}
 		
@@ -145,7 +151,7 @@ class AccountingEvaluatorsFactory {
 
 		@Override
 		public AssessmentEntryStatus getStatus(AssessmentEvaluation previousEvaluation,
-				AssessmentEvaluation currentEvaluation) {
+				AssessmentEvaluation currentEvaluation, boolean firstChild) {
 			return currentEvaluation.getAssessmentStatus();
 		}
 
diff --git a/src/main/java/org/olat/course/run/scoring/AssessmentAccounting.java b/src/main/java/org/olat/course/run/scoring/AssessmentAccounting.java
index c0b17d34646..f74940b0bba 100644
--- a/src/main/java/org/olat/course/run/scoring/AssessmentAccounting.java
+++ b/src/main/java/org/olat/course/run/scoring/AssessmentAccounting.java
@@ -57,7 +57,7 @@ public class AssessmentAccounting implements ScoreAccounting {
 	private final CourseConfig courseConfig;
 	private Map<String, AssessmentEntry> identToEntry = new HashMap<>();
 	private final Map<CourseNode, AssessmentEvaluation> courseNodeToEval = new HashMap<>();
-	private AssessmentEvaluation previosEvaluation;
+	private AssessmentEvaluation previousEvaluation;
 	
 	@Autowired
 	private CourseAssessmentService courseAssessmentService;
@@ -90,7 +90,7 @@ public class AssessmentAccounting implements ScoreAccounting {
 	
 	@Override
 	public boolean evaluateAll(boolean update) {
-		previosEvaluation = null;
+		previousEvaluation = null;
 		courseNodeToEval.clear();
 		
 		identToEntry = loadAssessmentEntries(getIdentity());
@@ -99,7 +99,7 @@ public class AssessmentAccounting implements ScoreAccounting {
 		fillCacheRecursiv(root);
 		
 		if (update) {
-			updateEntryRecursiv(root);
+			updateEntryRecursiv(root, true);
 		}
 		
 		return false;
@@ -145,7 +145,7 @@ public class AssessmentAccounting implements ScoreAccounting {
 		return entry;
 	}
 	
-	private AccountingResult updateEntryRecursiv(CourseNode courseNode) {
+	private AccountingResult updateEntryRecursiv(CourseNode courseNode, boolean firstChild) {
 		log.debug("Evaluate course node: type '{}', ident: '{}'", courseNode.getType(), courseNode.getIdent());
 		
 		AssessmentEvaluation currentEvaluation = courseNodeToEval.get(courseNode);
@@ -154,7 +154,7 @@ public class AssessmentAccounting implements ScoreAccounting {
 		AccountingEvaluators evaluators = courseAssessmentService.getEvaluators(courseNode, courseConfig);
 		
 		ObligationEvaluator obligationEvaluator = evaluators.getObligationEvaluator();
-		AssessmentObligation obligation = obligationEvaluator.getObligation(courseNode);
+		AssessmentObligation obligation = obligationEvaluator.getObligation(result, courseNode);
 		result.setObligation(obligation);
 		
 		DurationEvaluator durationEvaluator = evaluators.getDurationEvaluator();
@@ -174,17 +174,18 @@ public class AssessmentAccounting implements ScoreAccounting {
 		result.setPassed(passed);
 		
 		StatusEvaluator statusEvaluator = evaluators.getStatusEvaluator();
-		AssessmentEntryStatus status = statusEvaluator.getStatus(previosEvaluation, result);
+		AssessmentEntryStatus status = statusEvaluator.getStatus(previousEvaluation, result, firstChild);
 		result.setStatus(status);
 		
-		previosEvaluation = result;
+		previousEvaluation = result;
 		int childCount = courseNode.getChildCount();
 		List<AssessmentEvaluation> children = new ArrayList<>(childCount);
 		for (int i = 0; i < childCount; i++) {
 			INode child = courseNode.getChildAt(i);
+			firstChild = i== 0;
 			if (child instanceof CourseNode) {
 				CourseNode childCourseNode = (CourseNode) child;
-				AccountingResult childResult = updateEntryRecursiv(childCourseNode);
+				AccountingResult childResult = updateEntryRecursiv(childCourseNode, firstChild);
 				children.add(childResult);
 			}
 		}
@@ -199,6 +200,9 @@ public class AssessmentAccounting implements ScoreAccounting {
 		result.setLastUserModified(lastModifications.getLastUserModified());
 		result.setLastCoachModified(lastModifications.getLastCoachModified());
 		
+		obligation = obligationEvaluator.getObligation(result, children);
+		result.setObligation(obligation);
+		
 		CompletionEvaluator completionEvaluator = evaluators.getCompletionEvaluator();
 		Double completion = completionEvaluator.getCompletion(result, courseNode, this);
 		result.setCompletion(completion);
diff --git a/src/main/java/org/olat/course/run/scoring/ObligationEvaluator.java b/src/main/java/org/olat/course/run/scoring/ObligationEvaluator.java
index 74bbdad6958..0d22a418592 100644
--- a/src/main/java/org/olat/course/run/scoring/ObligationEvaluator.java
+++ b/src/main/java/org/olat/course/run/scoring/ObligationEvaluator.java
@@ -19,6 +19,8 @@
  */
 package org.olat.course.run.scoring;
 
+import java.util.List;
+
 import org.olat.course.nodes.CourseNode;
 import org.olat.modules.assessment.model.AssessmentObligation;
 
@@ -30,6 +32,8 @@ import org.olat.modules.assessment.model.AssessmentObligation;
  */
 public interface ObligationEvaluator {
 	
-	public AssessmentObligation getObligation(CourseNode courseNode);
+	public AssessmentObligation getObligation(AssessmentEvaluation currentEvaluation, CourseNode courseNode);
+
+	public AssessmentObligation getObligation(AssessmentEvaluation currentEvaluation, List<AssessmentEvaluation> children);
 
 }
diff --git a/src/main/java/org/olat/course/run/scoring/StatusEvaluator.java b/src/main/java/org/olat/course/run/scoring/StatusEvaluator.java
index 9c70e98d659..c20f4a90e64 100644
--- a/src/main/java/org/olat/course/run/scoring/StatusEvaluator.java
+++ b/src/main/java/org/olat/course/run/scoring/StatusEvaluator.java
@@ -31,7 +31,7 @@ import org.olat.modules.assessment.model.AssessmentEntryStatus;
  */
 public interface StatusEvaluator {
 	
-	public AssessmentEntryStatus getStatus(AssessmentEvaluation previousEvaluation, AssessmentEvaluation currentEvaluation);
+	public AssessmentEntryStatus getStatus(AssessmentEvaluation previousEvaluation, AssessmentEvaluation currentEvaluation, boolean firstChild);
 	
 	public AssessmentEntryStatus getStatus(AssessmentEvaluation currentEvaluation, List<AssessmentEvaluation>children);
 	
diff --git a/src/test/java/org/olat/course/learningpath/evaluation/DefaultLinearStatusEvaluatorTest.java b/src/test/java/org/olat/course/learningpath/evaluation/DefaultLinearStatusEvaluatorTest.java
index 9a17cde9281..fa740e41adb 100644
--- a/src/test/java/org/olat/course/learningpath/evaluation/DefaultLinearStatusEvaluatorTest.java
+++ b/src/test/java/org/olat/course/learningpath/evaluation/DefaultLinearStatusEvaluatorTest.java
@@ -48,7 +48,7 @@ public class DefaultLinearStatusEvaluatorTest {
 	public void shouldReturnDoneIfAssessmentStatusIsDone() {
 		AssessmentEvaluation assessmentEvaluation = getAssessmentEvaluation(null, done, null);
 		
-		AssessmentEntryStatus status = sut.getStatus(null, assessmentEvaluation);
+		AssessmentEntryStatus status = sut.getStatus(null, assessmentEvaluation, false);
 		
 		assertThat(status).isEqualTo(done);
 	}
@@ -57,7 +57,7 @@ public class DefaultLinearStatusEvaluatorTest {
 	public void shouldReturnReadyIfIsRootNodeAndNotAlreadyDone() {
 		AssessmentEvaluation assessmentEvaluation = getAssessmentEvaluation(null, notStarted, null);
 		
-		AssessmentEntryStatus status = sut.getStatus(null, assessmentEvaluation);
+		AssessmentEntryStatus status = sut.getStatus(null, assessmentEvaluation, false);
 		
 		assertThat(status).isEqualTo(notStarted);
 	}
@@ -66,7 +66,7 @@ public class DefaultLinearStatusEvaluatorTest {
 	public void shouldReturnReadyIfIsRootNodeAndIsNotStartedYet() {
 		AssessmentEvaluation assessmentEvaluation = getAssessmentEvaluation(null, notReady, null);
 		
-		AssessmentEntryStatus status = sut.getStatus(null, assessmentEvaluation);
+		AssessmentEntryStatus status = sut.getStatus(null, assessmentEvaluation, false);
 		
 		assertThat(status).isEqualTo(notStarted);
 	}
@@ -75,7 +75,7 @@ public class DefaultLinearStatusEvaluatorTest {
 	public void shouldReturnReadyIfIsRootNodeAndHasNoStatusYet() {
 		AssessmentEvaluation assessmentEvaluation = getAssessmentEvaluation(null, null, null);
 		
-		AssessmentEntryStatus status = sut.getStatus(null, assessmentEvaluation);
+		AssessmentEntryStatus status = sut.getStatus(null, assessmentEvaluation, false);
 		
 		assertThat(status).isEqualTo(notStarted);
 	}
@@ -123,10 +123,21 @@ public class DefaultLinearStatusEvaluatorTest {
 		AssessmentEvaluation previousEvaluation = getAssessmentEvaluation(previousFullyAssessd, previousStatus, previousObligation);
 		AssessmentEvaluation currentEvaluation = getAssessmentEvaluation(null, currentStatus, null);
 		
-		AssessmentEntryStatus status = sut.getStatus(previousEvaluation, currentEvaluation);
+		AssessmentEntryStatus status = sut.getStatus(previousEvaluation, currentEvaluation, false);
 		
 		assertThat(status).isEqualTo(expected);
 	}
+
+	@Test
+	public void shouldRetrunNotStartedIfisFirstChildAndPreviuosIsNotStartedAndMandatory() {
+		AssessmentEvaluation previousEvaluation = getAssessmentEvaluation(null, AssessmentEntryStatus.notStarted, AssessmentObligation.mandatory);
+		AssessmentEvaluation currentEvaluation = getAssessmentEvaluation(null, AssessmentEntryStatus.notReady, null);
+		
+		AssessmentEntryStatus status = sut.getStatus(previousEvaluation, currentEvaluation, true);
+		
+		assertThat(status).isEqualTo(AssessmentEntryStatus.notStarted);
+	}
+
 	
 	@Test
 	public void shouldNotChangeStatusWhenCheckingAgainstChildren() {
diff --git a/src/test/java/org/olat/course/nodes/st/assessment/MandatoryObligationEvaluatorTest.java b/src/test/java/org/olat/course/nodes/st/assessment/MandatoryObligationEvaluatorTest.java
new file mode 100644
index 00000000000..2275c2bca8d
--- /dev/null
+++ b/src/test/java/org/olat/course/nodes/st/assessment/MandatoryObligationEvaluatorTest.java
@@ -0,0 +1,82 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.course.nodes.st.assessment;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.Test;
+import org.olat.course.nodes.CourseNode;
+import org.olat.course.nodes.STCourseNode;
+import org.olat.course.run.scoring.AssessmentEvaluation;
+import org.olat.modules.assessment.model.AssessmentObligation;
+
+/**
+ * 
+ * Initial date: 23 Oct 2019<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+public class MandatoryObligationEvaluatorTest {
+	
+	private MandatoryObligationEvaluator sut = new MandatoryObligationEvaluator();
+	
+	@Test
+	public void shouldReturnMandatoryIfAtLeastOneChildIsMandatory() {
+		AssessmentEvaluation currentEvaluation = getAssessmentEvaluation(null);
+		AssessmentEvaluation child1 = getAssessmentEvaluation(AssessmentObligation.mandatory);
+		AssessmentEvaluation child2 = getAssessmentEvaluation(AssessmentObligation.optional);
+		List<AssessmentEvaluation> children = Arrays.asList(child1, child2);
+		
+		AssessmentObligation obligation = sut.getObligation(currentEvaluation, children);
+		
+		assertThat(obligation).isEqualTo(AssessmentObligation.mandatory);
+	}
+
+	@Test
+	public void shouldReturnOptionalIfAllChildrenAreOptional() {
+		AssessmentEvaluation currentEvaluation = getAssessmentEvaluation(null);
+		AssessmentEvaluation child1 = getAssessmentEvaluation(null);
+		AssessmentEvaluation child2 = getAssessmentEvaluation(AssessmentObligation.optional);
+		List<AssessmentEvaluation> children = Arrays.asList(child1, child2);
+		
+		AssessmentObligation obligation = sut.getObligation(currentEvaluation, children);
+		
+		assertThat(obligation).isEqualTo(AssessmentObligation.optional);
+
+	}
+	
+	@Test
+	public void shouldNotDependOnCourseNode() {
+		AssessmentEvaluation currentEvaluation = getAssessmentEvaluation(AssessmentObligation.optional);
+		CourseNode courseNode = new STCourseNode();
+		
+		AssessmentObligation obligation = sut.getObligation(currentEvaluation, courseNode);
+		
+		assertThat(obligation).isEqualTo(AssessmentObligation.optional);
+	}
+
+	private AssessmentEvaluation getAssessmentEvaluation(AssessmentObligation obligation) {
+		return new AssessmentEvaluation(null, null, null, null, null, null, null, null, null, null, null, null, 0, null, null, null, null, obligation, null);
+	}
+	
+}
diff --git a/src/test/java/org/olat/course/nodes/st/assessment/STLinearStatusEvaluatorTest.java b/src/test/java/org/olat/course/nodes/st/assessment/STLinearStatusEvaluatorTest.java
index 079dc0c8d44..9dc3815a459 100644
--- a/src/test/java/org/olat/course/nodes/st/assessment/STLinearStatusEvaluatorTest.java
+++ b/src/test/java/org/olat/course/nodes/st/assessment/STLinearStatusEvaluatorTest.java
@@ -25,7 +25,6 @@ import java.util.Arrays;
 import java.util.List;
 
 import org.junit.Test;
-import org.olat.course.nodes.st.assessment.STLinearStatusEvaluator;
 import org.olat.course.run.scoring.AssessmentEvaluation;
 import org.olat.modules.assessment.model.AssessmentEntryStatus;
 import org.olat.modules.assessment.model.AssessmentObligation;
@@ -56,28 +55,60 @@ public class STLinearStatusEvaluatorTest {
 	}
 	
 	@Test
-	public void shouldReturnInProgressIfChildIsDone() {
-		assertStatus(AssessmentEntryStatus.notStarted, AssessmentEntryStatus.done, AssessmentEntryStatus.inProgress);
+	public void shouldReturnNotStartedIfNoChildIsStarted() {
+		assertStatus(AssessmentEntryStatus.inProgress, AssessmentEntryStatus.notStarted, AssessmentEntryStatus.notStarted);
+	}
+	
+	private void assertStatus(AssessmentEntryStatus currentStatus, AssessmentEntryStatus childStatus, AssessmentEntryStatus expectedtStatus) {
+		AssessmentEvaluation currentEvaluation = getAssessmentEvaluation(null, currentStatus, null);
+		AssessmentEvaluation child = getAssessmentEvaluation(Boolean.TRUE, childStatus, AssessmentObligation.mandatory);
+		List<AssessmentEvaluation> children = Arrays.asList(child);
+		
+		AssessmentEntryStatus status = sut.getStatus(currentEvaluation, children);
+		
+		assertThat(status).isEqualTo(expectedtStatus);
 	}
 	
 	@Test
-	public void shouldReturnInProgessEvenIfChildIsNot() {
-		assertStatus(AssessmentEntryStatus.inProgress, AssessmentEntryStatus.notStarted, AssessmentEntryStatus.inProgress);
+	public void shouldReturnDoneIfAllMandatoryChildrenAreDone() {
+		AssessmentEvaluation currentEvaluation = getAssessmentEvaluation(Boolean.FALSE, AssessmentEntryStatus.inProgress, null);
+		AssessmentEvaluation child1 = getAssessmentEvaluation(Boolean.TRUE, AssessmentEntryStatus.done,
+				AssessmentObligation.mandatory);
+		AssessmentEvaluation child2 = getAssessmentEvaluation(Boolean.TRUE, AssessmentEntryStatus.inProgress,
+				AssessmentObligation.optional);
+		List<AssessmentEvaluation> children = Arrays.asList(child1, child2);
+		
+		AssessmentEntryStatus status = sut.getStatus(currentEvaluation, children);
+		
+		assertThat(status).isEqualTo(AssessmentEntryStatus.done);
 	}
 	
 	@Test
-	public void shouldReturnDoneEvenIfChildIsNot() {
-		assertStatus(AssessmentEntryStatus.done, AssessmentEntryStatus.notStarted, AssessmentEntryStatus.done);
+	public void shouldNotReturnDoneIfNotAllMandatoryChildrenAreDone() {
+		AssessmentEvaluation currentEvaluation = getAssessmentEvaluation(Boolean.TRUE, AssessmentEntryStatus.done, null);
+		AssessmentEvaluation child1 = getAssessmentEvaluation(Boolean.TRUE, AssessmentEntryStatus.inProgress,
+				AssessmentObligation.mandatory);
+		AssessmentEvaluation child2 = getAssessmentEvaluation(Boolean.TRUE, AssessmentEntryStatus.done,
+				AssessmentObligation.optional);
+		List<AssessmentEvaluation> children = Arrays.asList(child1, child2);
+		
+		AssessmentEntryStatus status = sut.getStatus(currentEvaluation, children);
+		
+		assertThat(status).isEqualTo(AssessmentEntryStatus.inProgress);
 	}
 	
-	public void assertStatus(AssessmentEntryStatus currentStatus, AssessmentEntryStatus childStatus, AssessmentEntryStatus expectedtStatus) {
-		AssessmentEvaluation currentEvaluation = getAssessmentEvaluation(null, currentStatus, null);
-		AssessmentEvaluation child = getAssessmentEvaluation(Boolean.TRUE, childStatus, null);
-		List<AssessmentEvaluation> children = Arrays.asList(child);
+	@Test
+	public void shouldReturnDoneIfItHAsOnlyOptionalChildren() {
+		AssessmentEvaluation currentEvaluation = getAssessmentEvaluation(Boolean.TRUE, AssessmentEntryStatus.notStarted, null);
+		AssessmentEvaluation child1 = getAssessmentEvaluation(Boolean.FALSE, AssessmentEntryStatus.inProgress,
+				AssessmentObligation.optional);
+		AssessmentEvaluation child2 = getAssessmentEvaluation(Boolean.FALSE, AssessmentEntryStatus.notStarted,
+				AssessmentObligation.optional);
+		List<AssessmentEvaluation> children = Arrays.asList(child1, child2);
 		
 		AssessmentEntryStatus status = sut.getStatus(currentEvaluation, children);
 		
-		assertThat(status).isEqualTo(expectedtStatus);
+		assertThat(status).isEqualTo(AssessmentEntryStatus.done);
 	}
 
 	private AssessmentEvaluation getAssessmentEvaluation(Boolean fullyAssessd, AssessmentEntryStatus assessmentStatus, AssessmentObligation obligation) {
diff --git a/src/test/java/org/olat/test/AllTestsJunit4.java b/src/test/java/org/olat/test/AllTestsJunit4.java
index 1d1c513cbe7..996dfac9a88 100644
--- a/src/test/java/org/olat/test/AllTestsJunit4.java
+++ b/src/test/java/org/olat/test/AllTestsJunit4.java
@@ -460,6 +460,7 @@ import org.junit.runners.Suite;
 	org.olat.course.learningpath.evaluation.LinearAccessEvaluator.class,
 	org.olat.course.learningpath.manager.LearningPathNodeAccessProviderTest.class,
 	org.olat.course.nodes.st.assessment.CumulatingDurationEvaluatorTest.class,
+	org.olat.course.nodes.st.assessment.MandatoryObligationEvaluatorTest.class,
 	org.olat.course.nodes.st.assessment.STFullyAssessedEvaluatorTest.class,
 	org.olat.course.nodes.st.assessment.STLastModificationsEvaluatorTest.class,
 	org.olat.course.nodes.st.assessment.STLinearStatusEvaluatorTest.class,
-- 
GitLab