diff --git a/src/main/java/org/olat/course/config/ui/CourseScoreController.java b/src/main/java/org/olat/course/config/ui/CourseScoreController.java index 925dd13e654928d4a8c1308690d3b90743994b6b..66d18906f8055fa92580cafa435bc25abe03d195 100644 --- a/src/main/java/org/olat/course/config/ui/CourseScoreController.java +++ b/src/main/java/org/olat/course/config/ui/CourseScoreController.java @@ -59,10 +59,12 @@ public class CourseScoreController extends FormBasicController { private static final String[] UNTRANSLATED = { "" }; private SingleSelection scoreEl; + private MultipleSelectionElement passedManuallyEl; private MultipleSelectionElement passedProgressEl; private MultipleSelectionElement passedAllEl; + private MultipleSelectionElement passedNumberEl; + private TextElement passedNumberCutEl; private MultipleSelectionElement passedPointsEl; - private MultipleSelectionElement passedManuallyEl; private TextElement passedPointsCutEl; private final RepositoryEntry courseEntry; @@ -133,7 +135,14 @@ public class CourseScoreController extends FormBasicController { String scoreKey = moduleConfig.has(STCourseNode.CONFIG_SCORE_KEY)? moduleConfig.getStringValue(STCourseNode.CONFIG_SCORE_KEY): SCORE_VALUE_NONE; scoreEl.select(scoreKey, true); - // Passed + // Passed manually + passedManuallyEl = uifactory.addCheckboxesHorizontal("options.passed.manually", formLayout, ONE_OPTION, UNTRANSLATED); + passedManuallyEl.addActionListener(FormEvent.ONCHANGE); + boolean passedManually = moduleConfig.getBooleanSafe(STCourseNode.CONFIG_PASSED_MANUALLY); + passedManuallyEl.select(passedManuallyEl.getKey(0), passedManually); + passedManuallyEl.setEnabled(editable); + + // Passed evaluation StaticTextElement passedIfEl = uifactory.addStaticTextElement("options.passed.if", null, formLayout); passedIfEl.setHelpTextKey("options.passed.if.help", null); @@ -147,6 +156,20 @@ public class CourseScoreController extends FormBasicController { passedAllEl.select(passedAllEl.getKey(0), passedAll); passedAllEl.setEnabled(editable); + passedNumberEl = uifactory.addCheckboxesHorizontal("options.passed.number", formLayout, ONE_OPTION, UNTRANSLATED); + passedNumberEl.addActionListener(FormEvent.ONCHANGE); + boolean passedNumber = moduleConfig.getBooleanSafe(STCourseNode.CONFIG_PASSED_NUMBER); + passedNumberEl.select(passedNumberEl.getKey(0), passedNumber); + passedNumberEl.setEnabled(editable); + + String passedNumberCut = moduleConfig.has(STCourseNode.CONFIG_PASSED_NUMBER_CUT) + ? String.valueOf(moduleConfig.getIntegerSafe(STCourseNode.CONFIG_PASSED_NUMBER_CUT, 1)) + : null; + passedNumberCutEl = uifactory.addTextElement("options.passed.number.cut", 10, passedNumberCut, formLayout); + passedNumberCutEl.setCheckVisibleLength(true); + passedNumberCutEl.setDisplaySize(10); + passedNumberEl.setEnabled(editable); + passedPointsEl = uifactory.addCheckboxesHorizontal("options.passed.points", formLayout, ONE_OPTION, UNTRANSLATED); passedPointsEl.addActionListener(FormEvent.ONCHANGE); boolean passedPoints = moduleConfig.getBooleanSafe(STCourseNode.CONFIG_PASSED_POINTS); @@ -161,12 +184,6 @@ public class CourseScoreController extends FormBasicController { passedPointsCutEl.setDisplaySize(10); passedPointsEl.setEnabled(editable); - passedManuallyEl = uifactory.addCheckboxesHorizontal("options.passed.manually", formLayout, ONE_OPTION, UNTRANSLATED); - passedManuallyEl.addActionListener(FormEvent.ONCHANGE); - boolean passedManually = moduleConfig.getBooleanSafe(STCourseNode.CONFIG_PASSED_MANUALLY); - passedManuallyEl.select(passedManuallyEl.getKey(0), passedManually); - passedManuallyEl.setEnabled(editable); - if (editable) { FormLayoutContainer buttonCont = FormLayoutContainer.createButtonLayout("buttons", getTranslator()); buttonCont.setRootForm(mainForm); @@ -178,13 +195,18 @@ public class CourseScoreController extends FormBasicController { } private void updateUI() { + boolean passedNumber = passedNumberEl.isAtLeastSelected(1); + passedNumberCutEl.setVisible(passedNumber); + boolean passedPoints = passedPointsEl.isAtLeastSelected(1); passedPointsCutEl.setVisible(passedPoints); } @Override protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { - if (source == passedPointsEl) { + if (source == passedNumberEl) { + updateUI(); + } else if (source == passedPointsEl) { updateUI(); } super.formInnerEvent(ureq, source, event); @@ -194,6 +216,7 @@ public class CourseScoreController extends FormBasicController { protected boolean validateFormLogic(UserRequest ureq) { boolean allOk = true; + allOk &= validateInteger(passedNumberCutEl, 1, Integer.MAX_VALUE, true, "error.positiv.int"); allOk &= validateInteger(passedPointsCutEl, 1, Integer.MAX_VALUE, true, "error.positiv.int"); return allOk & super.validateFormLogic(ureq); @@ -250,6 +273,15 @@ public class CourseScoreController extends FormBasicController { editorConfig.setStringValue(STCourseNode.CONFIG_SCORE_KEY, selectedScoreKey); } + boolean passedManually = passedManuallyEl.isAtLeastSelected(1); + if (passedManually) { + runConfig.setBooleanEntry(STCourseNode.CONFIG_PASSED_MANUALLY, true); + editorConfig.setBooleanEntry(STCourseNode.CONFIG_PASSED_MANUALLY, true); + } else { + runConfig.remove(STCourseNode.CONFIG_PASSED_MANUALLY); + editorConfig.remove(STCourseNode.CONFIG_PASSED_MANUALLY); + } + boolean passedProgress = passedProgressEl.isAtLeastSelected(1); if (passedProgress) { runConfig.setBooleanEntry(STCourseNode.CONFIG_PASSED_PROGRESS, true); @@ -268,6 +300,20 @@ public class CourseScoreController extends FormBasicController { editorConfig.remove(STCourseNode.CONFIG_PASSED_ALL); } + boolean passedNumber = passedNumberEl.isAtLeastSelected(1); + if (passedNumber) { + int numberCut = Integer.parseInt(passedNumberCutEl.getValue()); + runConfig.setBooleanEntry(STCourseNode.CONFIG_PASSED_NUMBER, true); + runConfig.setIntValue(STCourseNode.CONFIG_PASSED_NUMBER_CUT, numberCut); + editorConfig.setBooleanEntry(STCourseNode.CONFIG_PASSED_NUMBER, true); + editorConfig.setIntValue(STCourseNode.CONFIG_PASSED_NUMBER_CUT, numberCut); + } else { + runConfig.remove(STCourseNode.CONFIG_PASSED_NUMBER); + runConfig.remove(STCourseNode.CONFIG_PASSED_NUMBER_CUT); + editorConfig.remove(STCourseNode.CONFIG_PASSED_NUMBER); + editorConfig.remove(STCourseNode.CONFIG_PASSED_NUMBER_CUT); + } + boolean passedPoints = passedPointsEl.isAtLeastSelected(1); if (passedPoints) { int pointsCut = Integer.parseInt(passedPointsCutEl.getValue()); @@ -282,15 +328,6 @@ public class CourseScoreController extends FormBasicController { editorConfig.remove(STCourseNode.CONFIG_PASSED_POINTS_CUT); } - boolean passedManually = passedManuallyEl.isAtLeastSelected(1); - if (passedManually) { - runConfig.setBooleanEntry(STCourseNode.CONFIG_PASSED_MANUALLY, true); - editorConfig.setBooleanEntry(STCourseNode.CONFIG_PASSED_MANUALLY, true); - } else { - runConfig.remove(STCourseNode.CONFIG_PASSED_MANUALLY); - editorConfig.remove(STCourseNode.CONFIG_PASSED_MANUALLY); - } - CourseFactory.saveCourse(courseEntry.getOlatResource().getResourceableId()); CourseFactory.closeCourseEditSession(course.getResourceableId(), true); diff --git a/src/main/java/org/olat/course/config/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/config/ui/_i18n/LocalStrings_de.properties index 20708f53e46f5adc6acb981cb2aaa053687b4169..98cd915dda75bc99d6c7142d01df23a6eb51c2d8 100644 --- a/src/main/java/org/olat/course/config/ui/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/course/config/ui/_i18n/LocalStrings_de.properties @@ -45,13 +45,15 @@ glossary.no.glossary=Kein Glossar gew\u00E4hlt glossary.title=Titel lock.failed=Die Kurseinstellungen werden seit {1} vom Benutzer {0} editiert. options.efficency.title=Leistungnachweis -options.passed.all=... all Kursbausteine bestanden sind +options.passed.all=Alle Kursbausteine bestanden options.passed.if=Bestanden, wenn ... options.passed.if.help=Der Teilnehmer muss mindestens eines der Kriterien erf\u00FCllen, um den Kurs zu bestehen. options.passed.manually=Coach kann "bestanden" manuell setzen -options.passed.points=... Punkteschwelle erreicht ist +options.passed.number=Anzahl Kursbausteine bestanden +options.passed.number.cut=Anzahl Kursbausteine +options.passed.points=Punkteschwelle erreicht options.passed.points.cut=Punkteschwelle -options.passed.progress=... Lernfortschritt 100% ist +options.passed.progress=Lernfortschritt 100% options.score.points=Punkte options.score.points.average=Durchschnitt options.score.points.none=Keine Punkte diff --git a/src/main/java/org/olat/course/config/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/config/ui/_i18n/LocalStrings_en.properties index 4d0fc7edb137c0ecbddc8119b0515284db578c99..04039041552f24deed7e6704a6f959e846cf3a68 100644 --- a/src/main/java/org/olat/course/config/ui/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/course/config/ui/_i18n/LocalStrings_en.properties @@ -45,13 +45,15 @@ glossary.no.glossary=No glossary selected glossary.title=Title lock.failed=These course settings are edited since {1} by user {0}. options.efficency.title=Evidence of achievement -options.passed.all=... all course elements are passed +options.passed.all=All course elements passed options.passed.if=Passed if ... options.passed.if.help=The participant has to meet at least one criterion to pass the course. options.passed.manually=Coach can set "passed" manually -options.passed.points=... number of points is reached +options.passed.number=Number of course elements passed +options.passed.number.cut=Number of course elements +options.passed.points=Number of points reached options.passed.points.cut=Cut value -options.passed.progress=... learning progess is 100% +options.passed.progress=Learning progess 100% options.score.points=Points options.score.points.average=Average options.score.points.none=No points diff --git a/src/main/java/org/olat/course/nodes/STCourseNode.java b/src/main/java/org/olat/course/nodes/STCourseNode.java index bd0be8b85caf062d6c91bd80cf37aa38d8c173a7..936f25ea54a2216b00e6abd862e13eed12edc22e 100644 --- a/src/main/java/org/olat/course/nodes/STCourseNode.java +++ b/src/main/java/org/olat/course/nodes/STCourseNode.java @@ -116,6 +116,8 @@ public class STCourseNode extends AbstractAccessableCourseNode { public static final String CONFIG_SCORE_VALUE_AVG = "score.avg"; public static final String CONFIG_PASSED_PROGRESS = "passed.progress"; public static final String CONFIG_PASSED_ALL = "passed.all"; + public static final String CONFIG_PASSED_NUMBER = "passed.number"; + public static final String CONFIG_PASSED_NUMBER_CUT = "passed.number.cut"; public static final String CONFIG_PASSED_POINTS = "passed.points"; public static final String CONFIG_PASSED_POINTS_CUT = "passed.points.cut"; public static final String CONFIG_PASSED_MANUALLY = "passed.manually"; diff --git a/src/main/java/org/olat/course/nodes/st/assessment/STRootPassedEvaluator.java b/src/main/java/org/olat/course/nodes/st/assessment/STRootPassedEvaluator.java index 7977a13b1238ae2861cfab64a75a8d7392b55f5a..3bd663879868abd4bc951050b0b25342ee52af3d 100644 --- a/src/main/java/org/olat/course/nodes/st/assessment/STRootPassedEvaluator.java +++ b/src/main/java/org/olat/course/nodes/st/assessment/STRootPassedEvaluator.java @@ -71,13 +71,22 @@ public class STRootPassedEvaluator implements RootPassedEvaluator { if (config.getBooleanSafe(STCourseNode.CONFIG_PASSED_POINTS)) { Float score = currentEvaluation.getScore(); if (score != null) { - int cutvalue = config.getIntegerSafe(STCourseNode.CONFIG_PASSED_POINTS_CUT, Integer.MAX_VALUE); - if (score.floatValue() >= cutvalue) { + int cutValue = config.getIntegerSafe(STCourseNode.CONFIG_PASSED_POINTS_CUT, Integer.MAX_VALUE); + if (score.floatValue() >= cutValue) { return Boolean.TRUE; } } } + // Number passed + if (config.getBooleanSafe(STCourseNode.CONFIG_PASSED_NUMBER)) { + int cutValue = config.getIntegerSafe(STCourseNode.CONFIG_PASSED_NUMBER_CUT, Integer.MAX_VALUE); + Counts counts = passCounter.getCounts(courseNode, scoreAccounting); + if (counts.getPassed() >= cutValue) { + return Boolean.TRUE; + } + } + // All passed if (config.getBooleanSafe(STCourseNode.CONFIG_PASSED_ALL)) { Counts counts = passCounter.getCounts(courseNode, scoreAccounting); @@ -121,6 +130,9 @@ public class STRootPassedEvaluator implements RootPassedEvaluator { if (config.has(STCourseNode.CONFIG_PASSED_ALL)) { active++; } + if (config.has(STCourseNode.CONFIG_PASSED_NUMBER)) { + active++; + } if (config.has(STCourseNode.CONFIG_PASSED_POINTS)) { active++; } diff --git a/src/test/java/org/olat/course/nodes/st/assessment/STRootPassedEvaluatorTest.java b/src/test/java/org/olat/course/nodes/st/assessment/STRootPassedEvaluatorTest.java index 23a63d9772a9b99f8e4b45a12b0622fe9172a3df..77870d927e9c141811207a1cae2a4c306256062f 100644 --- a/src/test/java/org/olat/course/nodes/st/assessment/STRootPassedEvaluatorTest.java +++ b/src/test/java/org/olat/course/nodes/st/assessment/STRootPassedEvaluatorTest.java @@ -239,6 +239,58 @@ public class STRootPassedEvaluatorTest { assertThat(passed).isNull(); } + @Test + public void shouldReturnTrueIfNumberPassed() { + AssessmentEvaluation currentEvaluation = new AssessmentEvaluation(Float.valueOf(20), null, null, null); + CourseNode courseNode = new STCourseNode(); + courseNode.getModuleConfiguration().setBooleanEntry(STCourseNode.CONFIG_PASSED_NUMBER, true); + courseNode.getModuleConfiguration().setIntValue(STCourseNode.CONFIG_PASSED_NUMBER_CUT, 2); + ScoreAccounting scoreAccounting = new MappedScoreAccounting(); + + Counts counts = new CountsImpl(4, 2, 1); + PassCounter passCounter = mock(PassCounter.class); + when(passCounter.getCounts(any(), any())).thenReturn(counts); + STRootPassedEvaluator sut = new STRootPassedEvaluator(passCounter); + Boolean passed = sut.getPassed(currentEvaluation, courseNode, scoreAccounting, dummyEntry); + + assertThat(passed).isTrue(); + } + + @Test + public void shouldReturnNullIfNumberNotPassedAndOnlyCriterion() { + AssessmentEvaluation currentEvaluation = new AssessmentEvaluation(Float.valueOf(20), null, null, null); + CourseNode courseNode = new STCourseNode(); + courseNode.getModuleConfiguration().setBooleanEntry(STCourseNode.CONFIG_PASSED_NUMBER, true); + courseNode.getModuleConfiguration().setIntValue(STCourseNode.CONFIG_PASSED_NUMBER_CUT, 2); + ScoreAccounting scoreAccounting = new MappedScoreAccounting(); + + Counts counts = new CountsImpl(4, 1, 1); + PassCounter passCounter = mock(PassCounter.class); + when(passCounter.getCounts(any(), any())).thenReturn(counts); + STRootPassedEvaluator sut = new STRootPassedEvaluator(passCounter); + Boolean passed = sut.getPassed(currentEvaluation, courseNode, scoreAccounting, dummyEntry); + + assertThat(passed).isNull(); + } + + @Test + public void shouldNotReturnNullIfNumberNotPassedAndMultipleCriterion() { + AssessmentEvaluation currentEvaluation = new AssessmentEvaluation(Float.valueOf(20), null, null, null); + CourseNode courseNode = new STCourseNode(); + courseNode.getModuleConfiguration().setBooleanEntry(STCourseNode.CONFIG_PASSED_ALL, true); + courseNode.getModuleConfiguration().setBooleanEntry(STCourseNode.CONFIG_PASSED_NUMBER, true); + courseNode.getModuleConfiguration().setIntValue(STCourseNode.CONFIG_PASSED_NUMBER_CUT, 2); + ScoreAccounting scoreAccounting = new MappedScoreAccounting(); + + Counts counts = new CountsImpl(4, 1, 1); + PassCounter passCounter = mock(PassCounter.class); + when(passCounter.getCounts(any(), any())).thenReturn(counts); + STRootPassedEvaluator sut = new STRootPassedEvaluator(passCounter); + Boolean passed = sut.getPassed(currentEvaluation, courseNode, scoreAccounting, dummyEntry); + + assertThat(passed).isNull(); + } + @Test public void shouldReturnNullIfNotAllAssessed() { AssessmentEvaluation currentEvaluation = new AssessmentEvaluation(Float.valueOf(20), null, null, null);