From 3bab442a5dfc9837ae7bd5c24b8fcbc529a20c08 Mon Sep 17 00:00:00 2001 From: srosse <stephane.rosse@frentix.com> Date: Fri, 16 Oct 2020 10:21:17 +0200 Subject: [PATCH] OO-4934: handle compensation disadvantage in manual assessment modes --- .../fullWebApp/BaseFullWebappController.java | 28 ++- .../core/util/openxml/OpenXMLDocument.java | 2 +- .../course/assessment/AssessmentMode.java | 16 ++ .../AssessmentModeCoordinationService.java | 6 +- .../assessment/AssessmentModeManager.java | 34 ++-- ...AssessmentModeCoordinationServiceImpl.java | 157 ++++++++++++++--- .../assessment/manager/AssessmentModeDAO.java | 25 +-- .../manager/AssessmentModeManagerImpl.java | 21 ++- .../assessment/model/AssessmentModeImpl.java | 48 +++++- .../assessment/model/EnhancedStatus.java | 10 +- .../model/TransientAssessmentMode.java | 17 +- .../mode/AssessmentModeGuardController.java | 163 ++++++++++-------- .../ui/mode/AssessmentModeListModel.java | 101 +++++------ .../ui/mode/ModeStatusCellRenderer.java | 11 +- .../AssessmentModeOverviewListController.java | 5 +- .../AssessmentModeOverviewListTableModel.java | 3 +- .../ui/tool/AssessmentToolController.java | 13 +- .../ui/tool/_content/confirm_stop.html | 8 + .../ui/tool/_i18n/LocalStrings_de.properties | 3 + .../ui/tool/_i18n/LocalStrings_en.properties | 3 + .../java/org/olat/ims/qti21/QTI21Service.java | 2 + .../manager/AssessmentTestSessionDAO.java | 38 ++++ .../ims/qti21/manager/QTI21ServiceImpl.java | 5 + ...TracingReportGeneratorStep2Controller.java | 1 - .../DisadvantageCompensationService.java | 2 + .../manager/DisadvantageCompensationDAO.java | 24 +++ .../DisadvantageCompensationServiceImpl.java | 5 + .../database/mysql/alter_15_2_x_to_15_3_0.sql | 1 + .../database/mysql/setupDatabase.sql | 8 +- .../oracle/alter_15_2_x_to_15_3_0.sql | 1 + .../database/oracle/setupDatabase.sql | 1 + .../postgresql/alter_15_2_x_to_15_3_0.sql | 1 + .../database/postgresql/setupDatabase.sql | 1 + .../manager/AssessmentTestSessionDAOTest.java | 23 +++ .../DisadvantageCompensationDAOTest.java | 26 ++- 35 files changed, 608 insertions(+), 205 deletions(-) diff --git a/src/main/java/org/olat/core/commons/fullWebApp/BaseFullWebappController.java b/src/main/java/org/olat/core/commons/fullWebApp/BaseFullWebappController.java index 6a7335953a3..25d82d054c6 100644 --- a/src/main/java/org/olat/core/commons/fullWebApp/BaseFullWebappController.java +++ b/src/main/java/org/olat/core/commons/fullWebApp/BaseFullWebappController.java @@ -108,6 +108,7 @@ import org.olat.core.util.i18n.I18nManager; import org.olat.core.util.i18n.I18nModule; import org.olat.core.util.prefs.Preferences; import org.olat.core.util.resource.OresHelper; +import org.olat.course.assessment.AssessmentMode.EndStatus; import org.olat.course.assessment.AssessmentMode.Status; import org.olat.course.assessment.AssessmentModeNotificationEvent; import org.olat.course.assessment.model.TransientAssessmentMode; @@ -115,7 +116,9 @@ import org.olat.course.assessment.ui.mode.AssessmentModeGuardController; import org.olat.course.assessment.ui.mode.ChooseAssessmentModeEvent; import org.olat.gui.control.UserToolsMenuController; import org.olat.home.HomeSite; +import org.olat.modules.dcompensation.DisadvantageCompensationService; import org.olat.modules.edusharing.EdusharingModule; +import org.olat.repository.model.RepositoryEntryRefImpl; import org.olat.user.UserManager; import org.olat.user.propertyhandlers.UserPropertyHandler; import org.springframework.beans.factory.annotation.Autowired; @@ -1399,7 +1402,7 @@ public class BaseFullWebappController extends BasicController implements DTabs, if(getIdentity() == null || !event.isModeOf(lockMode, getIdentity())) { return; } - + String cmd = event.getCommand(); switch(cmd) { case AssessmentModeNotificationEvent.STOP_WARNING: @@ -1416,15 +1419,19 @@ public class BaseFullWebappController extends BasicController implements DTabs, } break; case AssessmentModeNotificationEvent.START_ASSESSMENT: - asyncLockResource(event.getAssessementMode()); + if(event.getAssessedIdentityKeys().contains(getIdentity().getKey())) { + asyncLockResource(event.getAssessementMode()); + } break; case AssessmentModeNotificationEvent.STOP_ASSESSMENT: - if(asyncLockResource(event.getAssessementMode())) { + if(event.getAssessedIdentityKeys().contains(getIdentity().getKey()) + && asyncLockResource(event.getAssessementMode())) { stickyMessageCmp.setDelegateComponent(null); } break; case AssessmentModeNotificationEvent.END: - if(asyncUnlockResource(event.getAssessementMode())) { + if(event.getAssessedIdentityKeys().contains(getIdentity().getKey()) + && asyncUnlockResource(event.getAssessementMode())) { stickyMessageCmp.setDelegateComponent(null); } break; @@ -1434,7 +1441,7 @@ public class BaseFullWebappController extends BasicController implements DTabs, @Override public boolean hasStaticSite(Class<? extends SiteInstance> type) { boolean hasSite = false; - if(sites != null && sites.size() > 0) { + if(sites != null && !sites.isEmpty()) { for(SiteInstance site:sites) { if(site.getClass().equals(type)) { hasSite = true; @@ -1523,8 +1530,10 @@ public class BaseFullWebappController extends BasicController implements DTabs, lock = true; lockMode = mode; lockStatus = LockStatus.need; - } else if(lockResource != null && lockResource.getResourceableId().equals(mode.getResource().getResourceableId())) { - if(mode.getStatus() == Status.leadtime || mode.getStatus() == Status.followup) { + } else if(lockResource.getResourceableId().equals(mode.getResource().getResourceableId())) { + if(mode.getStatus() == Status.leadtime + || (mode.getStatus() == Status.followup && mode.getEndStatus() == EndStatus.all) + || (mode.getStatus() == Status.followup && mode.getEndStatus() == EndStatus.withoutDisadvantage && !hasDisadvantageCompensation(mode))) { if(assessmentGuardCtrl == null) { lockStatus = LockStatus.need; } @@ -1537,6 +1546,11 @@ public class BaseFullWebappController extends BasicController implements DTabs, return lock; } + private boolean hasDisadvantageCompensation(TransientAssessmentMode mode) { + return CoreSpringFactory.getImpl(DisadvantageCompensationService.class) + .isActiveDisadvantageCompensation(getIdentity(), new RepositoryEntryRefImpl(mode.getRepositoryEntryKey()), mode.getElementList()); + } + private boolean asyncUnlockResource(TransientAssessmentMode mode) { boolean unlock; if(lockResource != null && lockResource.getResourceableId().equals(mode.getResource().getResourceableId())) { diff --git a/src/main/java/org/olat/core/util/openxml/OpenXMLDocument.java b/src/main/java/org/olat/core/util/openxml/OpenXMLDocument.java index 12f4621f1b8..85fa8353c64 100644 --- a/src/main/java/org/olat/core/util/openxml/OpenXMLDocument.java +++ b/src/main/java/org/olat/core/util/openxml/OpenXMLDocument.java @@ -78,7 +78,7 @@ public class OpenXMLDocument { private static final Logger log = Tracing.createLoggerFor(OpenXMLDocument.class); - private final int DPI = 72; + private static final int DPI = 72; private final Document document; private final Element rootElement; diff --git a/src/main/java/org/olat/course/assessment/AssessmentMode.java b/src/main/java/org/olat/course/assessment/AssessmentMode.java index 6c23ed804cf..07ff41c9d8f 100644 --- a/src/main/java/org/olat/course/assessment/AssessmentMode.java +++ b/src/main/java/org/olat/course/assessment/AssessmentMode.java @@ -20,6 +20,7 @@ package org.olat.course.assessment; import java.util.Date; +import java.util.List; import java.util.Set; import org.olat.core.id.CreateInfo; @@ -53,6 +54,10 @@ public interface AssessmentMode extends ModifiedInfo, CreateInfo { public void setStatus(Status status); + public EndStatus getEndStatus(); + + public void setEndStatus(EndStatus status); + public boolean isManualBeginEnd(); public void setManualBeginEnd(boolean manualBeginEnd); @@ -97,6 +102,12 @@ public interface AssessmentMode extends ModifiedInfo, CreateInfo { public void setElementList(String elementList); + /** + * + * @return A list of course elements identifiers or null if not defined + */ + public List<String> getElementAsList(); + public String getStartElement(); public void setStartElement(String startElement); @@ -150,4 +161,9 @@ public interface AssessmentMode extends ModifiedInfo, CreateInfo { return cssClass; } } + + public enum EndStatus { + all, + withoutDisadvantage + } } diff --git a/src/main/java/org/olat/course/assessment/AssessmentModeCoordinationService.java b/src/main/java/org/olat/course/assessment/AssessmentModeCoordinationService.java index 4c8e318f59e..038994c6fe4 100644 --- a/src/main/java/org/olat/course/assessment/AssessmentModeCoordinationService.java +++ b/src/main/java/org/olat/course/assessment/AssessmentModeCoordinationService.java @@ -50,6 +50,8 @@ public interface AssessmentModeCoordinationService { */ public boolean canStop(AssessmentMode assessmentMode); + public boolean isDisadvantageCompensationExtensionTime(AssessmentMode assessmentMode); + public void processRepositoryEntryChangedStatus(RepositoryEntry entry); @@ -57,9 +59,7 @@ public interface AssessmentModeCoordinationService { public AssessmentMode startAssessment(AssessmentMode assessmentMode); - public AssessmentMode stopAssessment(AssessmentMode assessmentMode); - - + public AssessmentMode stopAssessment(AssessmentMode assessmentMode, boolean pullTestSessions, boolean withDisadvantaged); public AssessmentModeStatistics getStatistics(AssessmentMode assessmentMode); diff --git a/src/main/java/org/olat/course/assessment/AssessmentModeManager.java b/src/main/java/org/olat/course/assessment/AssessmentModeManager.java index e6f3736c2a5..dcd4f2aefc1 100644 --- a/src/main/java/org/olat/course/assessment/AssessmentModeManager.java +++ b/src/main/java/org/olat/course/assessment/AssessmentModeManager.java @@ -113,7 +113,7 @@ public interface AssessmentModeManager { /** * Delete a specific assessment mode. * - * @param assessmentMode + * @param assessmentMode The assessment mode to delete */ public void delete(AssessmentMode assessmentMode); @@ -126,7 +126,7 @@ public interface AssessmentModeManager { /** * Search the whole assessment modes on the system. * - * @param params + * @param params The search parameters * @return A list of assessment modes */ public List<AssessmentMode> findAssessmentMode(SearchAssessmentModeParams params); @@ -149,15 +149,19 @@ public interface AssessmentModeManager { public List<AssessmentMode> getPlannedAssessmentMode(RepositoryEntryRef entry, Date from, Date to); /** - * Load the assessment mode for a specific user now. + * Load the assessment mode for a specific user now. The status + * of the assessment mode is checked and if the user has some + * disadvantage compensations. * - * @param identity - * @return + * @param identity The identity + * @return The active assessment mode for the specified user */ public List<AssessmentMode> getAssessmentModeFor(IdentityRef identity); /** * This return all modes between the begin date minus lead time and end time. + * Or if the assessment modes are stopped but some users with disadvantage + * compensations are still at work. * * @return The list of modes */ @@ -165,6 +169,8 @@ public interface AssessmentModeManager { /** * Return true if the course is in assessment mode at the specified time. + * Disadvantage compensations are taken in account. + * * @param entry The course * @param date The date * @return true if the course is in assessment mode @@ -185,27 +191,27 @@ public interface AssessmentModeManager { /** * Return the list of assessed users specified in the configuration. - * @param assessmentMode - * @return + * + * @param assessmentMode The assessment mode + * @return A list of identity keys */ public Set<Long> getAssessedIdentityKeys(AssessmentMode assessmentMode); public boolean isNodeInUse(RepositoryEntryRef entry, CourseNode node); - /** * - * @param ipList - * @param address - * @return + * @param ipList A list of IPs as string + * @param address An address, IP or domain + * @return true if the specified address match the ips list */ public boolean isIpAllowed(String ipList, String address); /** * - * @param request - * @param safeExamBrowserKey - * @return + * @param request The HTTP request + * @param safeExamBrowserKey The key + * @return true if the request is allowed based on the specified key */ public boolean isSafelyAllowed(HttpServletRequest request, String safeExamBrowserKeys); diff --git a/src/main/java/org/olat/course/assessment/manager/AssessmentModeCoordinationServiceImpl.java b/src/main/java/org/olat/course/assessment/manager/AssessmentModeCoordinationServiceImpl.java index 60e08330013..4588cee3928 100644 --- a/src/main/java/org/olat/course/assessment/manager/AssessmentModeCoordinationServiceImpl.java +++ b/src/main/java/org/olat/course/assessment/manager/AssessmentModeCoordinationServiceImpl.java @@ -26,6 +26,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; import org.apache.logging.log4j.Logger; import org.olat.basesecurity.IdentityRef; @@ -40,6 +41,7 @@ import org.olat.core.util.resource.OresHelper; import org.olat.core.util.session.UserSessionManager; import org.olat.course.CourseFactory; import org.olat.course.assessment.AssessmentMode; +import org.olat.course.assessment.AssessmentMode.EndStatus; import org.olat.course.assessment.AssessmentMode.Status; import org.olat.course.assessment.AssessmentModeCoordinationService; import org.olat.course.assessment.AssessmentModeNotificationEvent; @@ -49,6 +51,7 @@ import org.olat.course.assessment.model.AssessmentModeStatistics; import org.olat.course.assessment.model.CoordinatedAssessmentMode; import org.olat.course.assessment.model.TransientAssessmentMode; import org.olat.group.ui.edit.BusinessGroupModifiedEvent; +import org.olat.modules.dcompensation.manager.DisadvantageCompensationDAO; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryEntryStatusEnum; import org.olat.repository.manager.RepositoryEntryDAO; @@ -80,6 +83,8 @@ public class AssessmentModeCoordinationServiceImpl implements AssessmentModeCoor private UserSessionManager userSessionManager; @Autowired private AssessmentModeManagerImpl assessmentModeManager; + @Autowired + private DisadvantageCompensationDAO disadvantageCompensationDao; private Map<Long,CoordinatedAssessmentMode> coordinatedModes = new ConcurrentHashMap<>(); private CacheWrapper<Long,AssessmentModeStatistics> coordinatedModeStatistics; @@ -275,17 +280,7 @@ public class AssessmentModeCoordinationServiceImpl implements AssessmentModeCoor assessmentModeManager.getAssessedIdentityKeys(mode)); } } else if(mode.isManualBeginEnd() && !forceStatus) { - //what to do in manual mode - if(mode.getStatus() == Status.followup) { - if(mode.getEndWithFollowupTime().compareTo(now) < 0) { - mode = ensureStatusOfMode(mode, Status.end); - sendEvent(AssessmentModeNotificationEvent.END, mode, - assessmentModeManager.getAssessedIdentityKeys(mode)); - } - } else if(mode.getStatus() == Status.assessment) { - sendEvent(AssessmentModeNotificationEvent.START_ASSESSMENT, mode, - assessmentModeManager.getAssessedIdentityKeys(mode)); - } + sendEventForManualMode(mode, now); } else { if(mode.getBegin().compareTo(now) <= 0 && mode.getEnd().compareTo(now) > 0) { mode = ensureStatusOfMode(mode, Status.assessment); @@ -307,12 +302,12 @@ public class AssessmentModeCoordinationServiceImpl implements AssessmentModeCoor sendEvent(AssessmentModeNotificationEvent.STOP_ASSESSMENT, mode, assessmentModeManager.getAssessedIdentityKeys(mode)); } else { - mode = ensureStatusOfMode(mode, Status.end); + mode = ensureBothStatusOfMode(mode, Status.end, EndStatus.all); sendEvent(AssessmentModeNotificationEvent.END, mode, assessmentModeManager.getAssessedIdentityKeys(mode)); } } else if(mode.getEndWithFollowupTime().compareTo(now) <= 0) { - mode = ensureStatusOfMode(mode, Status.end); + mode = ensureBothStatusOfMode(mode, Status.end, EndStatus.all); sendEvent(AssessmentModeNotificationEvent.END, mode, assessmentModeManager.getAssessedIdentityKeys(mode)); } @@ -321,6 +316,38 @@ public class AssessmentModeCoordinationServiceImpl implements AssessmentModeCoor return mode; } + private void sendEventForManualMode(AssessmentMode mode, Date now) { + //what to do in manual mode + if(mode.getStatus() == Status.assessment) { + sendEvent(AssessmentModeNotificationEvent.START_ASSESSMENT, mode, + assessmentModeManager.getAssessedIdentityKeys(mode)); + } else if(mode.getStatus() == Status.followup) { + // remind user with compensation for disadvantage + if(mode.getEndStatus() == EndStatus.withoutDisadvantage) { + Set<Long> keys = getAssessedIdentitiesWithDisadvantageCompensations(mode); + sendEvent(AssessmentModeNotificationEvent.START_ASSESSMENT, mode, keys); + } + + // follow-up is ended + if(mode.getEndWithFollowupTime().compareTo(now) < 0) { + Set<Long> keys = getAssessedIdentitiesWithDisadvantageCompensations(mode); + if(keys.isEmpty() || mode.getEndStatus() == EndStatus.all) { + mode = ensureBothStatusOfMode(mode, Status.end, EndStatus.all); + sendEvent(AssessmentModeNotificationEvent.END, mode, + assessmentModeManager.getAssessedIdentityKeys(mode)); + } else { + mode = ensureBothStatusOfMode(mode, Status.end, EndStatus.withoutDisadvantage); + Set<Long> identitiesToNotify = assessmentModeManager.getAssessedIdentityKeys(mode); + identitiesToNotify.removeAll(keys); + sendEvent(AssessmentModeNotificationEvent.END, mode, identitiesToNotify); + } + } + } else if(mode.getStatus() == Status.end && mode.getEndStatus() == EndStatus.withoutDisadvantage) { + Set<Long> keys = getAssessedIdentitiesWithDisadvantageCompensations(mode); + sendEvent(AssessmentModeNotificationEvent.START_ASSESSMENT, mode, keys); + } + } + private AssessmentMode ensureStatusOfMode(AssessmentMode mode, Status status) { Status currentStatus = mode.getStatus(); if(currentStatus == null || currentStatus != status) { @@ -337,6 +364,24 @@ public class AssessmentModeCoordinationServiceImpl implements AssessmentModeCoor return mode; } + private AssessmentMode ensureBothStatusOfMode(AssessmentMode mode, Status status, EndStatus endStatus) { + Status currentStatus = mode.getStatus(); + EndStatus currentEndStatus = mode.getEndStatus(); + if(currentStatus == null || currentStatus != status || currentEndStatus != endStatus) { + mode.setStatus(status); + mode.setEndStatus(endStatus); + if(status == Status.assessment && mode.isManualBeginEnd()) { + syncBeginEndDate(mode); + } + mode = dbInstance.getCurrentEntityManager().merge(mode); + if(status == Status.leadtime || status == Status.assessment) { + warmUpAssessment(mode); + } + dbInstance.commit(); + } + return mode; + } + private void syncBeginEndDate(AssessmentMode mode) { Date programmedBegin = mode.getBegin(); @@ -388,7 +433,9 @@ public class AssessmentModeCoordinationServiceImpl implements AssessmentModeCoor public boolean canStop(AssessmentMode assessmentMode) { boolean canStop; Status status = assessmentMode.getStatus(); - if(status == Status.leadtime || status == Status.assessment) { + EndStatus endStatus = assessmentMode.getEndStatus(); + if(status == Status.leadtime || status == Status.assessment + || (endStatus == EndStatus.withoutDisadvantage && (status == Status.followup || status == Status.end))) { canStop = true; } else { canStop = false; @@ -396,6 +443,13 @@ public class AssessmentModeCoordinationServiceImpl implements AssessmentModeCoor return canStop; } + @Override + public boolean isDisadvantageCompensationExtensionTime(AssessmentMode assessmentMode) { + Status status = assessmentMode.getStatus(); + EndStatus endStatus = assessmentMode.getEndStatus(); + return endStatus == EndStatus.withoutDisadvantage && (status == Status.followup || status == Status.end); + } + @Override public AssessmentMode startAssessment(AssessmentMode mode) { mode = assessmentModeManager.getAssessmentModeById(mode.getKey()); @@ -406,32 +460,75 @@ public class AssessmentModeCoordinationServiceImpl implements AssessmentModeCoor } @Override - public AssessmentMode stopAssessment(AssessmentMode mode) { + public AssessmentMode stopAssessment(AssessmentMode mode, boolean pullTestSessions, boolean withDisadvantaged) { mode = assessmentModeManager.getAssessmentModeById(mode.getKey()); - Set<Long> assessedIdentityKeys = assessmentModeManager.getAssessedIdentityKeys(mode); + + EndStatus endStatus; + Set<Long> assessedIdentityKeys; + if(isDisadvantageCompensationExtensionTime(mode)) { + endStatus = withDisadvantaged ? EndStatus.all : EndStatus.withoutDisadvantage; + assessedIdentityKeys = getAssessedIdentitiesWithDisadvantageCompensations(mode); + } else { + assessedIdentityKeys = assessmentModeManager.getAssessedIdentityKeys(mode); + + boolean partial = false; + if(!withDisadvantaged) { + List<IdentityRef> disadvantagedIdentities = disadvantageCompensationDao + .getActiveDisadvantagedUsers(mode.getRepositoryEntry(), mode.getElementAsList()); + for(IdentityRef disadvantagedIdentity:disadvantagedIdentities) { + if(assessedIdentityKeys.remove(disadvantagedIdentity.getKey())) { + partial |= true; + } + } + } + + if(assessedIdentityKeys.contains(252744713l)) { + System.out.println(); + } + + endStatus = partial ? EndStatus.withoutDisadvantage : EndStatus.all; + } + + return stopAssessment(mode, assessedIdentityKeys, endStatus); + } + + private AssessmentMode stopAssessment(AssessmentMode mode, Set<Long> assessedIdentityKeys, EndStatus endStatus) { + String cmd; if(mode.getFollowupTime() > 0) { - Date now = new Date(); - Date followupTime = assessmentModeManager.evaluateFollowupTime(now, mode.getFollowupTime()); - ((AssessmentModeImpl)mode).setEnd(now); - ((AssessmentModeImpl)mode).setEndWithFollowupTime(followupTime); - mode.setStatus(Status.followup); + if(mode.getStatus() != Status.followup || mode.getStatus() != Status.end) { + Date now = new Date(); + Date followupTime = assessmentModeManager.evaluateFollowupTime(now, mode.getFollowupTime()); + ((AssessmentModeImpl)mode).setEnd(now); + ((AssessmentModeImpl)mode).setEndWithFollowupTime(followupTime); + mode.setStatus(Status.followup); + } + mode.setEndStatus(endStatus); mode = dbInstance.getCurrentEntityManager().merge(mode); - dbInstance.commit(); - sendEvent(AssessmentModeNotificationEvent.STOP_ASSESSMENT, mode, assessedIdentityKeys); + cmd = AssessmentModeNotificationEvent.STOP_ASSESSMENT; } else { - mode = ensureStatusOfMode(mode, Status.end); - sendEvent(AssessmentModeNotificationEvent.END, mode, assessedIdentityKeys); + mode = ensureBothStatusOfMode(mode, Status.end, endStatus); + cmd = AssessmentModeNotificationEvent.END; } + dbInstance.commit(); + sendEvent(cmd, mode, assessedIdentityKeys); return mode; } + /** + * This force the end of an assessment for all users. + * + * @param mode The assessment to end + * @return The merged assessment mode + */ private AssessmentMode endAssessment(AssessmentMode mode) { mode = assessmentModeManager.getAssessmentModeById(mode.getKey()); Set<Long> assessedIdentityKeys = assessmentModeManager.getAssessedIdentityKeys(mode); - mode = ensureStatusOfMode(mode, Status.end); + mode = ensureBothStatusOfMode(mode, Status.end, EndStatus.all); Date now = new Date(); ((AssessmentModeImpl)mode).setEnd(now); ((AssessmentModeImpl)mode).setEndWithFollowupTime(now); + mode = dbInstance.getCurrentEntityManager().merge(mode); + dbInstance.commit(); sendEvent(AssessmentModeNotificationEvent.END, mode, assessedIdentityKeys); return mode; } @@ -440,6 +537,14 @@ public class AssessmentModeCoordinationServiceImpl implements AssessmentModeCoor RepositoryEntry entry = repositoryEntryDao.loadByKey(mode.getRepositoryEntry().getKey()); CourseFactory.loadCourse(entry); } + + private Set<Long> getAssessedIdentitiesWithDisadvantageCompensations(AssessmentMode assessmentMode) { + List<IdentityRef> identities = disadvantageCompensationDao + .getActiveDisadvantagedUsers(assessmentMode.getRepositoryEntry(), assessmentMode.getElementAsList()); + return identities.stream() + .map(IdentityRef::getKey) + .collect(Collectors.toSet()); + } @Override public AssessmentModeStatistics getStatistics(AssessmentMode assessmentMode) { diff --git a/src/main/java/org/olat/course/assessment/manager/AssessmentModeDAO.java b/src/main/java/org/olat/course/assessment/manager/AssessmentModeDAO.java index f91316796a8..c11fc58c526 100644 --- a/src/main/java/org/olat/course/assessment/manager/AssessmentModeDAO.java +++ b/src/main/java/org/olat/course/assessment/manager/AssessmentModeDAO.java @@ -35,8 +35,10 @@ import org.olat.basesecurity.GroupRoles; import org.olat.basesecurity.IdentityRef; import org.olat.core.commons.persistence.DB; import org.olat.core.commons.persistence.PersistenceHelper; +import org.olat.core.commons.persistence.QueryBuilder; import org.olat.core.util.StringHelper; import org.olat.course.assessment.AssessmentMode; +import org.olat.course.assessment.AssessmentMode.EndStatus; import org.olat.course.assessment.AssessmentMode.Status; import org.olat.course.assessment.model.AssessmentModeImpl; import org.olat.course.assessment.model.SearchAssessmentModeParams; @@ -181,12 +183,12 @@ public class AssessmentModeDAO { cal.set(Calendar.MILLISECOND, 0); cal.set(Calendar.SECOND, 0); - StringBuilder sb = new StringBuilder(); + QueryBuilder sb = new QueryBuilder(); sb.append("select mode from courseassessmentmode mode where ") .append(" (mode.beginWithLeadTime<=:now and mode.endWithFollowupTime>=:now") .append(" and (mode.manualBeginEnd=false or (mode.manualBeginEnd=true and mode.leadTime>0)))") - .append(" or mode.statusString in ('").append(Status.leadtime.name()).append("','") - .append(Status.assessment.name()).append("','").append(Status.followup.name()).append("')"); + .append(" or mode.statusString ").in(Status.leadtime, Status.assessment, Status.followup) + .append(" or (mode.statusString ").in(Status.end.name()).append(" and mode.endStatusString ").in(EndStatus.withoutDisadvantage).append(")"); return dbInstance.getCurrentEntityManager() .createQuery(sb.toString(), AssessmentMode.class) @@ -200,13 +202,13 @@ public class AssessmentModeDAO { cal.set(Calendar.MILLISECOND, 0); cal.set(Calendar.SECOND, 0); - StringBuilder sb = new StringBuilder(); + QueryBuilder sb = new QueryBuilder(); sb.append("select count(mode) from courseassessmentmode mode where ") .append(" mode.repositoryEntry.key=:repoKey and (") .append(" (mode.beginWithLeadTime<=:now and mode.endWithFollowupTime>=:now ") .append(" and (mode.manualBeginEnd=false or (mode.manualBeginEnd=true and mode.leadTime>0)))") - .append(" or mode.statusString in ('").append(Status.leadtime.name()).append("','") - .append(Status.assessment.name()).append("','").append(Status.followup.name()).append("'))"); + .append(" or mode.statusString ").in(Status.leadtime, Status.assessment, Status.followup) + .append(" or (mode.statusString ").in(Status.end.name()).append(" and mode.endStatusString ").in(EndStatus.withoutDisadvantage).append("))"); List<Number> count = dbInstance.getCurrentEntityManager() .createQuery(sb.toString(), Number.class) @@ -222,14 +224,14 @@ public class AssessmentModeDAO { cal.set(Calendar.MILLISECOND, 0); cal.set(Calendar.SECOND, 0); - StringBuilder sb = new StringBuilder(512); + QueryBuilder sb = new QueryBuilder(); sb.append("select mode from courseassessmentmode mode") .append(" left join fetch mode.lectureBlock block") .append(" where mode.repositoryEntry.key=:repoKey and (") .append(" (mode.beginWithLeadTime<=:now and mode.endWithFollowupTime>=:now") .append(" and (mode.manualBeginEnd=false or (mode.manualBeginEnd=true and mode.leadTime>0)))") - .append(" or mode.statusString in ('").append(Status.leadtime.name()).append("','") - .append(Status.assessment.name()).append("','").append(Status.followup.name()).append("'))"); + .append(" or mode.statusString ").in(Status.leadtime, Status.assessment, Status.followup) + .append(" or (mode.statusString ").in(Status.end.name()).append(" and mode.endStatusString ").in(EndStatus.withoutDisadvantage).append("))"); return dbInstance.getCurrentEntityManager() .createQuery(sb.toString(), AssessmentMode.class) @@ -285,12 +287,13 @@ public class AssessmentModeDAO { public boolean isNodeInUse(RepositoryEntryRef entry, CourseNode node) { if(entry == null || node == null) return false; - StringBuilder sb = new StringBuilder(); + QueryBuilder sb = new QueryBuilder(); sb.append("select count(mode) from courseassessmentmode mode where ") .append(" mode.repositoryEntry.key=:repoKey ") .append(" and (mode.startElement=:startIdent or mode.elementList like :nodeIdent)") .append(" and (mode.beginWithLeadTime>=:now") - .append(" or mode.statusString in ('").append(Status.none.name()).append("','").append(Status.leadtime.name()).append("','").append(Status.assessment.name()).append("','").append(Status.followup.name()).append("'))"); + .append(" or mode.statusString ").in(Status.none, Status.leadtime, Status.assessment, Status.followup) + .append(" or mod.endStatusString ").in(EndStatus.withoutDisadvantage).append(")"); Calendar cal = Calendar.getInstance(); cal.set(Calendar.MILLISECOND, 0); diff --git a/src/main/java/org/olat/course/assessment/manager/AssessmentModeManagerImpl.java b/src/main/java/org/olat/course/assessment/manager/AssessmentModeManagerImpl.java index c4c6fdeb72b..d87b2221f5b 100644 --- a/src/main/java/org/olat/course/assessment/manager/AssessmentModeManagerImpl.java +++ b/src/main/java/org/olat/course/assessment/manager/AssessmentModeManagerImpl.java @@ -63,6 +63,7 @@ import org.olat.group.model.SearchBusinessGroupParams; import org.olat.modules.curriculum.CurriculumElement; import org.olat.modules.curriculum.CurriculumElementRef; import org.olat.modules.curriculum.manager.CurriculumElementDAO; +import org.olat.modules.dcompensation.manager.DisadvantageCompensationDAO; import org.olat.modules.lecture.LectureBlock; import org.olat.modules.lecture.manager.LectureBlockToGroupDAO; import org.olat.repository.RepositoryEntry; @@ -100,6 +101,8 @@ public class AssessmentModeManagerImpl implements AssessmentModeManager { @Autowired private LectureBlockToGroupDAO lectureBlockToGroupDao; @Autowired + private DisadvantageCompensationDAO disadvantageCompensationDao; + @Autowired private AssessmentModeCoordinationServiceImpl assessmentModeCoordinationService; @Override @@ -362,10 +365,26 @@ public class AssessmentModeManagerImpl implements AssessmentModeManager { List<AssessmentMode> myModes = null; if(!currentModes.isEmpty()) { //check permissions, groups, areas, course - myModes = assessmentModeDao.loadAssessmentModeFor(identity, currentModes); + List<AssessmentMode> allMyModes = assessmentModeDao.loadAssessmentModeFor(identity, currentModes); + + myModes = new ArrayList<>(allMyModes.size()); + for(AssessmentMode mode:allMyModes) { + if(assessmentModeCoordinationService.isDisadvantageCompensationExtensionTime(mode)) { + if(isDisadvantagedUser(mode, identity)) { + myModes.add(mode); + } + } else { + myModes.add(mode); + } + } } return myModes == null ? Collections.<AssessmentMode>emptyList() : myModes; } + + private boolean isDisadvantagedUser(AssessmentMode mode, IdentityRef identity) { + return disadvantageCompensationDao + .isActiveDisadvantagedUser(identity, mode.getRepositoryEntry(), mode.getElementAsList()); + } @Override public Set<Long> getAssessedIdentityKeys(AssessmentMode assessmentMode) { diff --git a/src/main/java/org/olat/course/assessment/model/AssessmentModeImpl.java b/src/main/java/org/olat/course/assessment/model/AssessmentModeImpl.java index 3484b356496..8fdc80147da 100644 --- a/src/main/java/org/olat/course/assessment/model/AssessmentModeImpl.java +++ b/src/main/java/org/olat/course/assessment/model/AssessmentModeImpl.java @@ -19,8 +19,10 @@ */ package org.olat.course.assessment.model; +import java.util.Arrays; import java.util.Date; import java.util.HashSet; +import java.util.List; import java.util.Set; import javax.persistence.CascadeType; @@ -36,6 +38,7 @@ import javax.persistence.OneToMany; import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.TemporalType; +import javax.persistence.Transient; import org.hibernate.annotations.GenericGenerator; import org.hibernate.annotations.Parameter; @@ -90,6 +93,8 @@ public class AssessmentModeImpl implements Persistable, AssessmentMode { @Column(name="a_status", nullable=true, insertable=true, updatable=true) private String statusString; + @Column(name="a_end_status", nullable=true, insertable=true, updatable=true) + private String endStatusString; @Temporal(TemporalType.TIMESTAMP) @Column(name="a_begin", nullable=false, insertable=true, updatable=true) @@ -207,18 +212,48 @@ public class AssessmentModeImpl implements Persistable, AssessmentMode { @Override public Status getStatus() { - return StringHelper.containsNonWhitespace(statusString) ? Status.valueOf(statusString) : Status.none; + return StringHelper.containsNonWhitespace(getStatusString()) ? Status.valueOf(getStatusString()) : Status.none; } @Override public void setStatus(Status status) { if(status == null) { - statusString = Status.none.name(); + setStatusString(Status.none.name()); } else { - statusString = status.name(); + setStatusString(status.name()); } } + public String getStatusString() { + return statusString; + } + + public void setStatusString(String statusString) { + this.statusString = statusString; + } + + @Override + public EndStatus getEndStatus() { + return StringHelper.containsNonWhitespace(getEndStatusString()) ? EndStatus.valueOf(getEndStatusString()) : null; + } + + @Override + public void setEndStatus(EndStatus status) { + if(status == null) { + setEndStatusString(null); + } else { + setEndStatusString(status.name()); + } + } + + public String getEndStatusString() { + return endStatusString; + } + + public void setEndStatusString(String endStatusString) { + this.endStatusString = endStatusString; + } + @Override public boolean isManualBeginEnd() { return manualBeginEnd; @@ -357,6 +392,13 @@ public class AssessmentModeImpl implements Persistable, AssessmentMode { public void setElementList(String elementList) { this.elementList = elementList; } + + @Override + @Transient + public List<String> getElementAsList() { + String nodes = getElementList(); + return StringHelper.containsNonWhitespace(nodes) ? Arrays.asList(nodes.split("[,]")) : null; + } @Override public String getStartElement() { diff --git a/src/main/java/org/olat/course/assessment/model/EnhancedStatus.java b/src/main/java/org/olat/course/assessment/model/EnhancedStatus.java index 583e046ebc0..9f454eaaba6 100644 --- a/src/main/java/org/olat/course/assessment/model/EnhancedStatus.java +++ b/src/main/java/org/olat/course/assessment/model/EnhancedStatus.java @@ -21,6 +21,7 @@ package org.olat.course.assessment.model; import java.util.List; +import org.olat.course.assessment.AssessmentMode.EndStatus; import org.olat.course.assessment.AssessmentMode.Status; /** @@ -32,11 +33,13 @@ import org.olat.course.assessment.AssessmentMode.Status; public class EnhancedStatus { private final Status status; + private final EndStatus endStatus; private final List<String> warnings; - public EnhancedStatus(Status status, List<String> warnings) { + public EnhancedStatus(Status status, EndStatus endStatus, List<String> warnings) { this.status = status; this.warnings = warnings; + this.endStatus = endStatus; } public List<String> getWarnings() { @@ -46,5 +49,8 @@ public class EnhancedStatus { public Status getStatus() { return status; } - + + public EndStatus getEndStatus() { + return endStatus; + } } diff --git a/src/main/java/org/olat/course/assessment/model/TransientAssessmentMode.java b/src/main/java/org/olat/course/assessment/model/TransientAssessmentMode.java index 27b6217fe9d..a306a8a5551 100644 --- a/src/main/java/org/olat/course/assessment/model/TransientAssessmentMode.java +++ b/src/main/java/org/olat/course/assessment/model/TransientAssessmentMode.java @@ -27,6 +27,7 @@ import java.util.List; import org.olat.core.id.OLATResourceable; import org.olat.core.util.resource.OresHelper; import org.olat.course.assessment.AssessmentMode; +import org.olat.course.assessment.AssessmentMode.EndStatus; import org.olat.course.assessment.AssessmentMode.Status; /** @@ -52,10 +53,12 @@ public class TransientAssessmentMode implements Serializable { private int leadTime; private int followupTime; private String startElementKey; + private List<String> elementList; - private boolean manual; + private final boolean manual; - private Status status; + private final Status status; + private final EndStatus endStatus; private String ipList; private String safeExamBrowserKey; @@ -77,10 +80,12 @@ public class TransientAssessmentMode implements Serializable { leadTime = mode.getLeadTime(); followupTime = mode.getFollowupTime(); startElementKey = mode.getStartElement(); + elementList = mode.getElementAsList(); manual = mode.isManualBeginEnd(); status = mode.getStatus(); + endStatus = mode.getEndStatus(); if(mode.isRestrictAccessIps()) { ipList = mode.getIpList(); @@ -139,6 +144,10 @@ public class TransientAssessmentMode implements Serializable { public Status getStatus() { return status; } + + public EndStatus getEndStatus() { + return endStatus; + } public Date getBegin() { return begin; @@ -180,6 +189,10 @@ public class TransientAssessmentMode implements Serializable { return startElementKey; } + public List<String> getElementList() { + return elementList; + } + public String getIpList() { return ipList; } diff --git a/src/main/java/org/olat/course/assessment/ui/mode/AssessmentModeGuardController.java b/src/main/java/org/olat/course/assessment/ui/mode/AssessmentModeGuardController.java index c1daecb11d3..35f64069143 100644 --- a/src/main/java/org/olat/course/assessment/ui/mode/AssessmentModeGuardController.java +++ b/src/main/java/org/olat/course/assessment/ui/mode/AssessmentModeGuardController.java @@ -45,11 +45,14 @@ import org.olat.core.util.Formatter; import org.olat.core.util.StringHelper; import org.olat.core.util.coordinate.CoordinatorManager; import org.olat.core.util.event.GenericEventListener; +import org.olat.course.assessment.AssessmentMode.EndStatus; import org.olat.course.assessment.AssessmentMode.Status; import org.olat.course.assessment.AssessmentModeCoordinationService; import org.olat.course.assessment.AssessmentModeManager; import org.olat.course.assessment.AssessmentModeNotificationEvent; import org.olat.course.assessment.model.TransientAssessmentMode; +import org.olat.modules.dcompensation.DisadvantageCompensationService; +import org.olat.repository.model.RepositoryEntryRefImpl; import org.springframework.beans.factory.annotation.Autowired; /** @@ -74,6 +77,8 @@ public class AssessmentModeGuardController extends BasicController implements Ge @Autowired private AssessmentModeManager assessmentModeMgr; @Autowired + private DisadvantageCompensationService disadvantageCompensationService; + @Autowired private AssessmentModeCoordinationService assessmentModeCoordinationService; /** @@ -159,7 +164,7 @@ public class AssessmentModeGuardController extends BasicController implements Ge Date beginWithLeadTime = mode.getBeginWithLeadTime(); Date endWithFollowupTime = mode.getEndWithFollowupTime(); //check if the mode must not be guarded anymore - if(mode.isManual() && (Status.end.equals(mode.getStatus()) || Status.none.equals(mode.getStatus()))) { + if(mode.isManual() && ((Status.end.equals(mode.getStatus()) && EndStatus.all.equals(mode.getEndStatus())) || Status.none.equals(mode.getStatus()))) { return null; } else if(!mode.isManual() && (beginWithLeadTime.after(now) || now.after(endWithFollowupTime))) { return null; @@ -214,79 +219,97 @@ public class AssessmentModeGuardController extends BasicController implements Ge private String updateButtons(TransientAssessmentMode mode, Date now, Link go, Link cont) { String state; if(mode.isManual()) { - if(Status.leadtime == mode.getStatus()) { - state = Status.leadtime.name(); - go.setEnabled(false); - go.setVisible(true); - cont.setEnabled(false); - cont.setVisible(false); - } else if(Status.assessment == mode.getStatus()) { - state = Status.assessment.name(); - go.setEnabled(true); - go.setVisible(true); - cont.setEnabled(false); - cont.setVisible(false); - } else if(Status.followup == mode.getStatus()) { - state = Status.followup.name(); - go.setEnabled(false); - go.setVisible(false); - cont.setEnabled(false); - cont.setVisible(false); - } else if(Status.end == mode.getStatus()) { - state = Status.end.name(); - go.setEnabled(false); - go.setVisible(false); - cont.setEnabled(true); - cont.setVisible(true); - } else { - state = "error"; - go.setEnabled(false); - go.setVisible(false); - cont.setEnabled(false); - cont.setVisible(false); - } + state = updateButtonsManual(mode, go, cont); } else { - - Date begin = mode.getBegin(); - Date beginWithLeadTime = mode.getBeginWithLeadTime(); - Date end = mode.getEnd(); - Date endWithLeadTime = mode.getEndWithFollowupTime(); - - if(beginWithLeadTime.compareTo(now) <= 0 && begin.compareTo(now) > 0) { - state = Status.leadtime.name(); - go.setEnabled(false); - go.setVisible(true); - cont.setEnabled(false); - cont.setVisible(false); - } else if(begin.compareTo(now) <= 0 && end.compareTo(now) > 0) { - state = Status.assessment.name(); - go.setEnabled(true); - go.setVisible(true); - cont.setEnabled(false); - cont.setVisible(false); - } else if(end.compareTo(now) <= 0 && endWithLeadTime.compareTo(now) > 0) { - state = Status.followup.name(); - go.setEnabled(false); - go.setVisible(false); - cont.setEnabled(false); - cont.setVisible(false); - } else if(endWithLeadTime.compareTo(now) <= 0 || Status.end == mode.getStatus()) { - state = Status.end.name(); - go.setEnabled(false); - go.setVisible(false); - cont.setEnabled(true); - cont.setVisible(true); - } else { - state = "error"; - go.setEnabled(false); - go.setVisible(false); - cont.setEnabled(false); - cont.setVisible(false); - } + state = updateButtonsAuto(mode, now, go, cont); + } + return state; + } + + private String updateButtonsManual(TransientAssessmentMode mode, Link go, Link cont) { + String state; + if(Status.leadtime == mode.getStatus()) { + state = Status.leadtime.name(); + go.setEnabled(false); + go.setVisible(true); + cont.setEnabled(false); + cont.setVisible(false); + } else if(Status.assessment == mode.getStatus() || isDisadvantageCompensationExtension(mode)) { + state = Status.assessment.name(); + go.setEnabled(true); + go.setVisible(true); + cont.setEnabled(false); + cont.setVisible(false); + } else if(Status.followup == mode.getStatus()) { + state = Status.followup.name(); + go.setEnabled(false); + go.setVisible(false); + cont.setEnabled(false); + cont.setVisible(false); + } else if(Status.end == mode.getStatus()) { + state = Status.end.name(); + go.setEnabled(false); + go.setVisible(false); + cont.setEnabled(true); + cont.setVisible(true); + } else { + state = "error"; + go.setEnabled(false); + go.setVisible(false); + cont.setEnabled(false); + cont.setVisible(false); } - return state; + } + + private boolean isDisadvantageCompensationExtension(TransientAssessmentMode mode) { + if(mode.getEndStatus() == EndStatus.withoutDisadvantage + && (mode.getStatus() == Status.followup || mode.getStatus() == Status.end)) { + return disadvantageCompensationService.isActiveDisadvantageCompensation(getIdentity(), + new RepositoryEntryRefImpl(mode.getRepositoryEntryKey()), mode.getElementList()); + } + return false; + } + + private String updateButtonsAuto(TransientAssessmentMode mode, Date now, Link go, Link cont) { + Date begin = mode.getBegin(); + Date beginWithLeadTime = mode.getBeginWithLeadTime(); + Date end = mode.getEnd(); + Date endWithLeadTime = mode.getEndWithFollowupTime(); + String state; + if(beginWithLeadTime.compareTo(now) <= 0 && begin.compareTo(now) > 0) { + state = Status.leadtime.name(); + go.setEnabled(false); + go.setVisible(true); + cont.setEnabled(false); + cont.setVisible(false); + } else if(begin.compareTo(now) <= 0 && end.compareTo(now) > 0) { + state = Status.assessment.name(); + go.setEnabled(true); + go.setVisible(true); + cont.setEnabled(false); + cont.setVisible(false); + } else if(end.compareTo(now) <= 0 && endWithLeadTime.compareTo(now) > 0) { + state = Status.followup.name(); + go.setEnabled(false); + go.setVisible(false); + cont.setEnabled(false); + cont.setVisible(false); + } else if(endWithLeadTime.compareTo(now) <= 0 || Status.end == mode.getStatus()) { + state = Status.end.name(); + go.setEnabled(false); + go.setVisible(false); + cont.setEnabled(true); + cont.setVisible(true); + } else { + state = "error"; + go.setEnabled(false); + go.setVisible(false); + cont.setEnabled(false); + cont.setVisible(false); + } + return state; } private ResourceGuard createGuard(TransientAssessmentMode mode) { diff --git a/src/main/java/org/olat/course/assessment/ui/mode/AssessmentModeListModel.java b/src/main/java/org/olat/course/assessment/ui/mode/AssessmentModeListModel.java index b861bfbfd6a..09471208704 100644 --- a/src/main/java/org/olat/course/assessment/ui/mode/AssessmentModeListModel.java +++ b/src/main/java/org/olat/course/assessment/ui/mode/AssessmentModeListModel.java @@ -35,7 +35,6 @@ import org.olat.course.CorruptedCourseException; import org.olat.course.CourseFactory; import org.olat.course.ICourse; import org.olat.course.assessment.AssessmentMode; -import org.olat.course.assessment.AssessmentMode.Status; import org.olat.course.assessment.AssessmentModeCoordinationService; import org.olat.course.assessment.model.EnhancedStatus; import org.olat.course.assessment.model.TransientAssessmentMode; @@ -50,6 +49,7 @@ import org.olat.course.nodes.CourseNode; public class AssessmentModeListModel extends DefaultFlexiTableDataModel<AssessmentMode> implements SortableFlexiTableDataModel<AssessmentMode> { private static final Logger log = Tracing.createLoggerFor(AssessmentModeListModel.class); + private static final Cols[] COLS = Cols.values(); private final Translator translator; private final AssessmentModeCoordinationService coordinationService; @@ -74,42 +74,8 @@ public class AssessmentModeListModel extends DefaultFlexiTableDataModel<Assessme @Override public Object getValueAt(AssessmentMode mode, int col) { - switch(Cols.values()[col]) { - case status: { - List<String> warnings = null; - Status status = mode.getStatus(); - try { - if(StringHelper.containsNonWhitespace(mode.getStartElement())) { - ICourse course = CourseFactory.loadCourse(mode.getRepositoryEntry()); - CourseNode node = course.getRunStructure().getNode(mode.getStartElement()); - if(node == null) { - warnings = new ArrayList<>(2); - warnings.add(translator.translate("warning.missing.start.element")); - } - } - if(StringHelper.containsNonWhitespace(mode.getElementList())) { - ICourse course = CourseFactory.loadCourse(mode.getRepositoryEntry()); - String elements = mode.getElementList(); - for(String element:elements.split(",")) { - CourseNode node = course.getRunStructure().getNode(element); - if(node == null) { - if(warnings == null) { - warnings = new ArrayList<>(2); - } - warnings.add(translator.translate("warning.missing.element")); - break; - } - } - } - } catch (CorruptedCourseException e) { - log.error("", e); - if(warnings == null) { - warnings = new ArrayList<>(2); - } - warnings.add(translator.translate("cif.error.corrupted")); - } - return new EnhancedStatus(status, warnings); - } + switch(COLS[col]) { + case status: return getStatus(mode); case course: return mode.getRepositoryEntry().getDisplayname(); case externalId: return mode.getRepositoryEntry().getExternalId(); case externalRef: return mode.getRepositoryEntry().getExternalRef(); @@ -119,22 +85,61 @@ public class AssessmentModeListModel extends DefaultFlexiTableDataModel<Assessme case leadTime: return mode.getLeadTime(); case followupTime: return mode.getFollowupTime(); case target: return mode.getTargetAudience(); - case start: { - boolean canStart = mode.isManualBeginEnd(); - if(canStart) { - canStart = coordinationService.canStart(mode); + case start: return canStart(mode); + case stop: return canStop(mode); + default: return "ERROR"; + } + } + + private boolean canStart(AssessmentMode mode) { + boolean canStart = mode.isManualBeginEnd(); + if(canStart) { + canStart = coordinationService.canStart(mode); + } + return canStart; + } + + private boolean canStop(AssessmentMode mode) { + boolean canStop = mode.isManualBeginEnd(); + if(canStop) { + canStop = coordinationService.canStop(mode); + } + return canStop; + } + + private EnhancedStatus getStatus(AssessmentMode mode) { + List<String> warnings = null; + try { + if(StringHelper.containsNonWhitespace(mode.getStartElement())) { + ICourse course = CourseFactory.loadCourse(mode.getRepositoryEntry()); + CourseNode node = course.getRunStructure().getNode(mode.getStartElement()); + if(node == null) { + warnings = new ArrayList<>(2); + warnings.add(translator.translate("warning.missing.start.element")); } - return canStart; } - case stop: { - boolean canStop = mode.isManualBeginEnd(); - if(canStop) { - canStop = coordinationService.canStop(mode); + if(StringHelper.containsNonWhitespace(mode.getElementList())) { + ICourse course = CourseFactory.loadCourse(mode.getRepositoryEntry()); + String elements = mode.getElementList(); + for(String element:elements.split(",")) { + CourseNode node = course.getRunStructure().getNode(element); + if(node == null) { + if(warnings == null) { + warnings = new ArrayList<>(2); + } + warnings.add(translator.translate("warning.missing.element")); + break; + } } - return canStop; } + } catch (CorruptedCourseException e) { + log.error("", e); + if(warnings == null) { + warnings = new ArrayList<>(2); + } + warnings.add(translator.translate("cif.error.corrupted")); } - return null; + return new EnhancedStatus(mode.getStatus(), mode.getEndStatus(), warnings); } @Override diff --git a/src/main/java/org/olat/course/assessment/ui/mode/ModeStatusCellRenderer.java b/src/main/java/org/olat/course/assessment/ui/mode/ModeStatusCellRenderer.java index f19b45cce62..8995f01c85f 100644 --- a/src/main/java/org/olat/course/assessment/ui/mode/ModeStatusCellRenderer.java +++ b/src/main/java/org/olat/course/assessment/ui/mode/ModeStatusCellRenderer.java @@ -27,6 +27,7 @@ import org.olat.core.gui.render.Renderer; import org.olat.core.gui.render.StringOutput; import org.olat.core.gui.render.URLBuilder; import org.olat.core.gui.translator.Translator; +import org.olat.course.assessment.AssessmentMode.EndStatus; import org.olat.course.assessment.AssessmentMode.Status; import org.olat.course.assessment.model.EnhancedStatus; @@ -50,11 +51,11 @@ public class ModeStatusCellRenderer implements FlexiCellRenderer { if(cellValue instanceof Status) { Status status = (Status)cellValue; - render(status, sb); + render(status, null, sb); } else if(cellValue instanceof EnhancedStatus) { EnhancedStatus enStatus = (EnhancedStatus)cellValue; renderWarning(enStatus.getWarnings(), sb); - render(enStatus.getStatus(), sb); + render(enStatus.getStatus(), enStatus.getEndStatus(), sb); } } @@ -68,8 +69,10 @@ public class ModeStatusCellRenderer implements FlexiCellRenderer { } } - private void render(Status status, StringOutput sb) { + private void render(Status status, EndStatus endStatus, StringOutput sb) { String title = helper.getStatusLabel(status); - sb.append("<span title='").append(title).append("'><i class='o_icon ").append(status.cssClass()).append("'> </i></span>"); + sb.append("<span title='").append(title).append("'><i class='o_icon ").append(status.cssClass()).append("'> </i>") + .append(" <i class='o_icon o_icon_disadvantage_compensation'> </i>", EndStatus.withoutDisadvantage == endStatus) + .append("</span>"); } } \ No newline at end of file diff --git a/src/main/java/org/olat/course/assessment/ui/tool/AssessmentModeOverviewListController.java b/src/main/java/org/olat/course/assessment/ui/tool/AssessmentModeOverviewListController.java index 3278988d0f7..3fc9931a415 100644 --- a/src/main/java/org/olat/course/assessment/ui/tool/AssessmentModeOverviewListController.java +++ b/src/main/java/org/olat/course/assessment/ui/tool/AssessmentModeOverviewListController.java @@ -200,8 +200,6 @@ public class AssessmentModeOverviewListController extends FormBasicController im boolean endSoon = (endInMillseconds < (5l * 60l * 1000l)) && (mode.getStatus() == Status.assessment || mode.getStatus() == Status.followup); - - AssessmentModeOverviewRow row = new AssessmentModeOverviewRow(mode, isToday, endSoon, endInMillseconds); LectureBlock block = mode.getLectureBlock(); @@ -223,6 +221,9 @@ public class AssessmentModeOverviewListController extends FormBasicController im FormLink endButton = uifactory.addFormLink(id, "end", "end", null, flc, Link.BUTTON_SMALL); endButton.setDomReplacementWrapperRequired(false); endButton.setIconLeftCSS("o_icon o_icon-fw o_as_mode_stop"); + if(assessmentModeCoordinationService.isDisadvantageCompensationExtensionTime(mode)) { + endButton.setIconRightCSS("o_icon o_icon-fw o_icon_disadvantage_compensation"); + } endButton.setUserObject(row); flc.add(id, endButton); forgeStatistics(mode, row); diff --git a/src/main/java/org/olat/course/assessment/ui/tool/AssessmentModeOverviewListTableModel.java b/src/main/java/org/olat/course/assessment/ui/tool/AssessmentModeOverviewListTableModel.java index b6e7b8d4edb..f1a195b1bbc 100644 --- a/src/main/java/org/olat/course/assessment/ui/tool/AssessmentModeOverviewListTableModel.java +++ b/src/main/java/org/olat/course/assessment/ui/tool/AssessmentModeOverviewListTableModel.java @@ -42,7 +42,8 @@ public class AssessmentModeOverviewListTableModel extends DefaultFlexiTableDataM public Object getValueAt(int row, int col) { AssessmentModeOverviewRow mode = getObject(row); switch(ModeCols.values()[col]) { - case status: return new EnhancedStatus(mode.getAssessmentMode().getStatus(), Collections.emptyList()); + case status: return new EnhancedStatus(mode.getAssessmentMode().getStatus(), + mode.getAssessmentMode().getEndStatus(), Collections.emptyList()); case name: return mode.getAssessmentMode().getName(); case begin: return mode.getAssessmentMode().getBegin(); case end: return mode.getAssessmentMode().getEnd(); diff --git a/src/main/java/org/olat/course/assessment/ui/tool/AssessmentToolController.java b/src/main/java/org/olat/course/assessment/ui/tool/AssessmentToolController.java index 1c0e72e89a5..755d06ff5bc 100644 --- a/src/main/java/org/olat/course/assessment/ui/tool/AssessmentToolController.java +++ b/src/main/java/org/olat/course/assessment/ui/tool/AssessmentToolController.java @@ -51,6 +51,7 @@ import org.olat.core.util.resource.OresHelper; import org.olat.course.CourseFactory; import org.olat.course.ICourse; import org.olat.course.assessment.AssessmentMode; +import org.olat.course.assessment.AssessmentModeCoordinationService; import org.olat.course.assessment.AssessmentModeManager; import org.olat.course.assessment.AssessmentModule; import org.olat.course.assessment.CourseAssessmentService; @@ -104,11 +105,13 @@ public class AssessmentToolController extends MainLayoutBasicController implemen @Autowired private CourseAssessmentService courseAssessmentService; @Autowired + private NodeAccessService nodeAccessService; + @Autowired private AssessmentService assessmentService; @Autowired private AssessmentModeManager assessmentModeManager; @Autowired - private NodeAccessService nodeAccessService; + private AssessmentModeCoordinationService assessmentModeCoordinationService; public AssessmentToolController(UserRequest ureq, WindowControl wControl, TooledStackedPanel stackPanel, RepositoryEntry courseEntry, UserCourseEnvironment coachUserEnv, AssessmentToolSecurityCallback assessmentCallback) { @@ -141,6 +144,9 @@ public class AssessmentToolController extends MainLayoutBasicController implemen String label = translate("assessment.tool.stop", new String[] { StringHelper.escapeHtml(modeName) }); stopAssessmentMode = LinkFactory.createCustomLink("assessment.stop", "stop", label, Link.BUTTON_SMALL | Link.NONTRANSLATED, warn, this); stopAssessmentMode.setIconLeftCSS("o_icon o_icon-fw o_as_mode_stop"); + if(assessmentModeCoordinationService.isDisadvantageCompensationExtensionTime(mode)) { + stopAssessmentMode.setIconRightCSS("o_icon o_icon-fw o_icon_disadvantage_compensation"); + } stopAssessmentMode.setUserObject(mode); } } else { @@ -193,12 +199,11 @@ public class AssessmentToolController extends MainLayoutBasicController implemen private boolean canStopAssessmentMode(AssessmentMode mode) { if(assessmentCallback.canStartStopAllAssessments()) { - return mode.getStatus() == AssessmentMode.Status.assessment || mode.getStatus() == AssessmentMode.Status.leadtime; + return assessmentModeCoordinationService.canStop(mode); } else if(mode.getLectureBlock() != null) { List<Identity> teachers = lectureService.getTeachers(mode.getLectureBlock()); return teachers.contains(getIdentity()) - && (mode.getStatus() == AssessmentMode.Status.assessment - || mode.getStatus() == AssessmentMode.Status.leadtime); + && assessmentModeCoordinationService.canStop(mode); } return false; } diff --git a/src/main/java/org/olat/course/assessment/ui/tool/_content/confirm_stop.html b/src/main/java/org/olat/course/assessment/ui/tool/_content/confirm_stop.html index e94e80be276..9b93db40ae1 100644 --- a/src/main/java/org/olat/course/assessment/ui/tool/_content/confirm_stop.html +++ b/src/main/java/org/olat/course/assessment/ui/tool/_content/confirm_stop.html @@ -3,6 +3,14 @@ <p>$compensationMsg</p> #end </div> +<div> +#if($r.available("disadvantages")) + $r.render("disadvantages") +#end +#if($r.available("runningSessions")) + $r.render("runningSessions") +#end +</div> <div class="o_button_group"> $r.render("cancel") $r.render("stop") diff --git a/src/main/java/org/olat/course/assessment/ui/tool/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/assessment/ui/tool/_i18n/LocalStrings_de.properties index a9f160eaa29..ae749b47d79 100644 --- a/src/main/java/org/olat/course/assessment/ui/tool/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/course/assessment/ui/tool/_i18n/LocalStrings_de.properties @@ -33,10 +33,13 @@ command.previous.node=Zur\u00FCck zum letzten Kursbaustein confirm.certificate.deleted=$org.olat.course.certificate.ui\:confirm.certificate.deleted confirm.delete.certificate.text=$org.olat.course.certificate.ui\:confirm.delete.certificate.text confirm.delete.certificate.title=$org.olat.course.certificate.ui\:confirm.delete.certificate.title +confirm.stop.pull.running.sessions=Den Tests automatisch ziehen. confirm.stop.title=Pr\u00FCfung beenden confirm.stop.text.compensations=Es gibt <strong>ein Person</strong> mit Nachteilausgleich. confirm.stop.text.compensations.plural=Es gibt <strong>{0} Personen</strong> mit Nachteilausgleich. confirm.stop.text.details=Wollen Sie die Pr\u00FCfung "{0}" jetzt beenden? +confirm.stop.final.text.details=Wollen Sie die Pr\u00FCfung "{0}" jetzt beenden? Es betrifft noch die Personen mit Nachteilausgleich. +confirm.stop.with.disadvantages=Pr\u00FCfung f\u00FCr Personen mit Nachteilausgleich auch beenden. elements.to.review=<i class\="o_icon o_icon_warning"> </i> {0} unerledigt end=Beenden filter=$org.olat.modules.assessment.ui\:filter diff --git a/src/main/java/org/olat/course/assessment/ui/tool/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/assessment/ui/tool/_i18n/LocalStrings_en.properties index 3cb4bf42d63..d4749674d7e 100644 --- a/src/main/java/org/olat/course/assessment/ui/tool/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/course/assessment/ui/tool/_i18n/LocalStrings_en.properties @@ -33,10 +33,13 @@ command.previous.node=Back to previous course element confirm.certificate.deleted=$org.olat.course.certificate.ui\:confirm.certificate.deleted confirm.delete.certificate.text=$org.olat.course.certificate.ui\:confirm.delete.certificate.text confirm.delete.certificate.title=$org.olat.course.certificate.ui\:confirm.delete.certificate.title +confirm.stop.pull.running.sessions=Pull the tests automatically. confirm.stop.text.compensations=There is <strong>a person</strong> with a compensation for disadvantages. confirm.stop.text.compensations.plural=There is <strong>{0} persons</strong> with compensations for disadvantages. confirm.stop.text.details=Do you want to end the exam "{0}" now? +confirm.stop.final.text.details=Do you want to end the exam "{0}" for the persons with disadvantage compensations now? confirm.stop.title=End the exam +confirm.stop.with.disadvantages=End the exam for the persons with a compensation for disadvantages too. elements.to.review=<i class\="o_icon o_icon_warning"> </i> {0} pending end=End filter=$org.olat.modules.assessment.ui\:filter diff --git a/src/main/java/org/olat/ims/qti21/QTI21Service.java b/src/main/java/org/olat/ims/qti21/QTI21Service.java index c838a2e6b35..81835ca50d5 100644 --- a/src/main/java/org/olat/ims/qti21/QTI21Service.java +++ b/src/main/java/org/olat/ims/qti21/QTI21Service.java @@ -286,6 +286,8 @@ public interface QTI21Service { public boolean isRunningAssessmentTestSession(RepositoryEntry entry, String subIdent, RepositoryEntry testEntry, List<? extends IdentityRef> identities); + public boolean isRunningAssessmentTestSession(RepositoryEntry entry, List<String> subIdents, List<? extends IdentityRef> identities); + /** * Add some extra time to an assessment test session. * diff --git a/src/main/java/org/olat/ims/qti21/manager/AssessmentTestSessionDAO.java b/src/main/java/org/olat/ims/qti21/manager/AssessmentTestSessionDAO.java index 923dcabd0f3..ff74f2f7bb4 100644 --- a/src/main/java/org/olat/ims/qti21/manager/AssessmentTestSessionDAO.java +++ b/src/main/java/org/olat/ims/qti21/manager/AssessmentTestSessionDAO.java @@ -556,6 +556,44 @@ public class AssessmentTestSessionDAO { return found != null && !found.isEmpty() && found.get(0) != null && found.get(0) >= 0; } + /** + * + * @param entry The repository entry (typically the course, or the test if not in a course) (mandatory) + * @param courseSubIdent An optional sub-identifier + * @param testEntry The test repository entry + * @param identities The list of assessed identities + * @return true if at least one of the identities has a running test session + */ + public boolean hasRunningTestSessions(RepositoryEntryRef entry, List<String> courseSubIdents, List<? extends IdentityRef> identities) { + StringBuilder sb = new StringBuilder(); + sb.append("select session.key from qtiassessmenttestsession session") + .append(" left join session.testEntry testEntry") + .append(" left join testEntry.olatResource testResource") + .append(" where session.repositoryEntry.key=:repositoryEntryKey") + .append(" and session.finishTime is null and session.terminationTime is null") + .append(" and session.exploded=false and session.cancelled=false") + .append(" and session.identity.key in (:identityKeys)"); + if(courseSubIdents != null && !courseSubIdents.isEmpty()) { + sb.append(" and session.subIdent in (:subIdents)"); + } + + List<Long> identityKeys = identities.stream() + .map(IdentityRef::getKey) + .collect(Collectors.toList()); + + TypedQuery<Long> query = dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), Long.class) + .setFirstResult(0) + .setMaxResults(1) + .setParameter("repositoryEntryKey", entry.getKey()) + .setParameter("identityKeys", identityKeys); + if(courseSubIdents != null && !courseSubIdents.isEmpty()) { + query.setParameter("subIdents", courseSubIdents); + } + List<Long> found = query.getResultList(); + return found != null && !found.isEmpty() && found.get(0) != null && found.get(0) >= 0; + } + public int deleteTestSession(AssessmentTestSession testSession) { StringBuilder responseSb = new StringBuilder(); responseSb.append("delete from qtiassessmentresponse response where response.assessmentTestSession.key=:sessionKey"); diff --git a/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java b/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java index 1631bb85cea..93508b8d62e 100644 --- a/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java +++ b/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java @@ -660,6 +660,11 @@ public class QTI21ServiceImpl implements QTI21Service, UserDataDeletable, Initia public boolean isRunningAssessmentTestSession(RepositoryEntry entry, String subIdent, RepositoryEntry testEntry, List<? extends IdentityRef> identities) { return testSessionDao.hasRunningTestSessions(entry, subIdent, testEntry, identities); } + + @Override + public boolean isRunningAssessmentTestSession(RepositoryEntry entry, List<String> subIdents, List<? extends IdentityRef> identities) { + return testSessionDao.hasRunningTestSessions(entry, subIdents, identities); + } @Override public List<AssessmentTestSession> getRunningAssessmentTestSession(RepositoryEntry entry, String subIdent, RepositoryEntry testEntry) { diff --git a/src/main/java/org/olat/modules/contacttracing/ui/ContactTracingReportGeneratorStep2Controller.java b/src/main/java/org/olat/modules/contacttracing/ui/ContactTracingReportGeneratorStep2Controller.java index 4a301b24379..4a70b2a424d 100644 --- a/src/main/java/org/olat/modules/contacttracing/ui/ContactTracingReportGeneratorStep2Controller.java +++ b/src/main/java/org/olat/modules/contacttracing/ui/ContactTracingReportGeneratorStep2Controller.java @@ -31,7 +31,6 @@ import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.generic.wizard.StepFormBasicController; import org.olat.core.gui.control.generic.wizard.StepsEvent; import org.olat.core.gui.control.generic.wizard.StepsRunContext; -import org.olat.course.editor.overview.YesNoCellRenderer; import org.olat.modules.contacttracing.ContactTracingManager; import org.olat.modules.contacttracing.ui.ContactTracingLocationTableModel.ContactTracingLocationCols; import org.springframework.beans.factory.annotation.Autowired; diff --git a/src/main/java/org/olat/modules/dcompensation/DisadvantageCompensationService.java b/src/main/java/org/olat/modules/dcompensation/DisadvantageCompensationService.java index 744f7ff55ea..d9d88542c52 100644 --- a/src/main/java/org/olat/modules/dcompensation/DisadvantageCompensationService.java +++ b/src/main/java/org/olat/modules/dcompensation/DisadvantageCompensationService.java @@ -57,6 +57,8 @@ public interface DisadvantageCompensationService { */ public DisadvantageCompensation getActiveDisadvantageCompensation(IdentityRef identity, RepositoryEntryRef entry, String subIdent); + public boolean isActiveDisadvantageCompensation(IdentityRef identity, RepositoryEntryRef entry, List<String> subIdents); + /** * @param entry The course * @param subIdents A list of course elements (optional, null means all) diff --git a/src/main/java/org/olat/modules/dcompensation/manager/DisadvantageCompensationDAO.java b/src/main/java/org/olat/modules/dcompensation/manager/DisadvantageCompensationDAO.java index 76f36f6d227..c0376828eac 100644 --- a/src/main/java/org/olat/modules/dcompensation/manager/DisadvantageCompensationDAO.java +++ b/src/main/java/org/olat/modules/dcompensation/manager/DisadvantageCompensationDAO.java @@ -155,4 +155,28 @@ public class DisadvantageCompensationDAO { .map(IdentityRefImpl::new) .collect(Collectors.toList()); } + + public boolean isActiveDisadvantagedUser(IdentityRef identity, RepositoryEntryRef entry, List<String> subIdents) { + QueryBuilder sb = new QueryBuilder(); + sb.append("select compensation.key from dcompensation as compensation") + .append(" where compensation.identity.key=:identityKey") + .append(" and compensation.entry.key=:entryKey and compensation.status=:status"); + if(subIdents != null && !subIdents.isEmpty()) { + sb.append(" and compensation.subIdent in (:subIdent)"); + } + + TypedQuery<Long> query = dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), Long.class) + .setFirstResult(0) + .setMaxResults(1) + .setParameter("status", DisadvantageCompensationStatusEnum.active.name()) + .setParameter("identityKey", identity.getKey()) + .setParameter("entryKey", entry.getKey()); + if(subIdents != null && !subIdents.isEmpty()) { + query.setParameter("subIdent", subIdents); + } + + List<Long> keys = query.getResultList(); + return keys != null && !keys.isEmpty() && keys.get(0) != null && keys.get(0).longValue() > 0; + } } diff --git a/src/main/java/org/olat/modules/dcompensation/manager/DisadvantageCompensationServiceImpl.java b/src/main/java/org/olat/modules/dcompensation/manager/DisadvantageCompensationServiceImpl.java index 8200178ef55..191fd425969 100644 --- a/src/main/java/org/olat/modules/dcompensation/manager/DisadvantageCompensationServiceImpl.java +++ b/src/main/java/org/olat/modules/dcompensation/manager/DisadvantageCompensationServiceImpl.java @@ -81,6 +81,11 @@ public class DisadvantageCompensationServiceImpl implements DisadvantageCompensa return disadvantageCompensationDao.getActiveDisadvantageCompensation(identity, entry, subIdent); } + @Override + public boolean isActiveDisadvantageCompensation(IdentityRef identity, RepositoryEntryRef entry, List<String> subIdents) { + return disadvantageCompensationDao.isActiveDisadvantagedUser(identity, entry, subIdents); + } + @Override public List<IdentityRef> getActiveDisadvantagedUsers(RepositoryEntryRef entry, List<String> subIdents) { return disadvantageCompensationDao.getActiveDisadvantagedUsers(entry, subIdents); diff --git a/src/main/resources/database/mysql/alter_15_2_x_to_15_3_0.sql b/src/main/resources/database/mysql/alter_15_2_x_to_15_3_0.sql index cca46347424..e4e3513fbc4 100644 --- a/src/main/resources/database/mysql/alter_15_2_x_to_15_3_0.sql +++ b/src/main/resources/database/mysql/alter_15_2_x_to_15_3_0.sql @@ -30,6 +30,7 @@ create unique index idx_de_userinfo_ident_idx on o_de_user_info(fk_identity); -- Assessment alter table o_as_entry add column a_current_run_start datetime; +alter table o_as_mode_course add column a_end_status varchar(32); -- Disadvantage compensation alter table o_qti_assessmenttest_session add column q_compensation_extra_time bigint; diff --git a/src/main/resources/database/mysql/setupDatabase.sql b/src/main/resources/database/mysql/setupDatabase.sql index 121a1d3a8f3..60c1ced1b61 100644 --- a/src/main/resources/database/mysql/setupDatabase.sql +++ b/src/main/resources/database/mysql/setupDatabase.sql @@ -1387,6 +1387,7 @@ create table o_as_mode_course ( a_name varchar(255), a_description longtext, a_status varchar(16), + a_end_status varchar(32), a_manual_beginend bit not null default 0, a_begin datetime not null, a_leadtime bigint not null default 0, @@ -4290,9 +4291,10 @@ alter table o_ap_appointment add constraint ap_appointment_meeting_idx foreign k alter table o_ap_participation add constraint ap_part_appointment_idx foreign key (fk_appointment_id) references o_ap_appointment (id); alter table o_ap_participation add constraint ap_part_identity_idx foreign key (fk_identity_id) references o_bs_identity (id); -insert into hibernate_unique_key values ( 0 ); -SET FOREIGN_KEY_CHECKS = 1; - -- Organiation role rights alter table o_org_role_to_right add constraint org_role_to_right_to_organisation_idx foreign key (fk_organisation) references o_org_organisation (id); create index idx_org_role_to_right_to_organisation_idx on o_org_role_to_right (fk_organisation); + +insert into hibernate_unique_key values ( 0 ); +SET FOREIGN_KEY_CHECKS = 1; + diff --git a/src/main/resources/database/oracle/alter_15_2_x_to_15_3_0.sql b/src/main/resources/database/oracle/alter_15_2_x_to_15_3_0.sql index 3d26a1b9f1f..b8857782f37 100644 --- a/src/main/resources/database/oracle/alter_15_2_x_to_15_3_0.sql +++ b/src/main/resources/database/oracle/alter_15_2_x_to_15_3_0.sql @@ -27,6 +27,7 @@ create unique index idx_de_userinfo_ident_idx on o_de_user_info(fk_identity); -- Assessment alter table o_as_entry add a_current_run_start timestamp; +alter table o_as_mode_course add a_end_status varchar(32); -- Disadvantage compensation alter table o_qti_assessmenttest_session add q_compensation_extra_time number(20); diff --git a/src/main/resources/database/oracle/setupDatabase.sql b/src/main/resources/database/oracle/setupDatabase.sql index de0f8880725..5a62656d4f1 100644 --- a/src/main/resources/database/oracle/setupDatabase.sql +++ b/src/main/resources/database/oracle/setupDatabase.sql @@ -1449,6 +1449,7 @@ create table o_as_mode_course ( a_name varchar2(255 char), a_description clob, a_status varchar2(16 char), + a_end_status varchar(32), a_manual_beginend number default 0 not null, a_begin date not null, a_leadtime number(20) default 0 not null, diff --git a/src/main/resources/database/postgresql/alter_15_2_x_to_15_3_0.sql b/src/main/resources/database/postgresql/alter_15_2_x_to_15_3_0.sql index d3f69ebd400..77ae7f7be77 100644 --- a/src/main/resources/database/postgresql/alter_15_2_x_to_15_3_0.sql +++ b/src/main/resources/database/postgresql/alter_15_2_x_to_15_3_0.sql @@ -28,6 +28,7 @@ create unique index idx_de_userinfo_ident_idx on o_de_user_info(fk_identity); -- Assessment alter table o_as_entry add column a_current_run_start timestamp; +alter table o_as_mode_course add column a_end_status varchar(32); -- Disadvantage compensation alter table o_qti_assessmenttest_session add column q_compensation_extra_time int8; diff --git a/src/main/resources/database/postgresql/setupDatabase.sql b/src/main/resources/database/postgresql/setupDatabase.sql index 2d3f7821c51..85f847b3ec7 100644 --- a/src/main/resources/database/postgresql/setupDatabase.sql +++ b/src/main/resources/database/postgresql/setupDatabase.sql @@ -1409,6 +1409,7 @@ create table o_as_mode_course ( a_name varchar(255), a_description text, a_status varchar(16), + a_end_status varchar(32), a_manual_beginend bool not null default false, a_begin timestamp not null, a_leadtime int8 not null default 0, diff --git a/src/test/java/org/olat/ims/qti21/manager/AssessmentTestSessionDAOTest.java b/src/test/java/org/olat/ims/qti21/manager/AssessmentTestSessionDAOTest.java index 446185a7e91..48b57dd4f25 100644 --- a/src/test/java/org/olat/ims/qti21/manager/AssessmentTestSessionDAOTest.java +++ b/src/test/java/org/olat/ims/qti21/manager/AssessmentTestSessionDAOTest.java @@ -450,6 +450,29 @@ public class AssessmentTestSessionDAOTest extends OlatTestCase { Assert.assertTrue(hasAtLeastOneRunningTestSession); } + @Test + public void hasRunningTestSessions_subIdentList() { + // prepare a test and a user + RepositoryEntry testEntry = JunitTestHelper.createAndPersistRepositoryEntry(); + Identity assessedIdentity = JunitTestHelper.createAndPersistIdentityAsRndUser("session-30"); + Identity otherIdentity = JunitTestHelper.createAndPersistIdentityAsRndUser("session-31"); + String subIdent = "OO-1234"; + AssessmentEntry assessmentEntry = assessmentService.getOrCreateAssessmentEntry(assessedIdentity, null, testEntry, subIdent, Boolean.FALSE, testEntry); + dbInstance.commit(); + + //create an assessment test session + AssessmentTestSession testSession = testSessionDao.createAndPersistTestSession(testEntry, testEntry, subIdent, assessmentEntry, assessedIdentity, null, null, false); + Assert.assertNotNull(testSession); + dbInstance.commitAndCloseSession(); + + //check + boolean hasRunningTestSessions = testSessionDao.hasRunningTestSessions(testEntry, List.of(subIdent), List.of(assessedIdentity)); + Assert.assertTrue(hasRunningTestSessions); + + boolean hasNotRunningTestSessions = testSessionDao.hasRunningTestSessions(testEntry, List.of(subIdent), List.of(otherIdentity)); + Assert.assertFalse(hasNotRunningTestSessions); + } + @Test public void getAllUserTestSessions() { // prepare a test and a user diff --git a/src/test/java/org/olat/modules/dcompensation/manager/DisadvantageCompensationDAOTest.java b/src/test/java/org/olat/modules/dcompensation/manager/DisadvantageCompensationDAOTest.java index a1cec066069..4d70f26ba7b 100644 --- a/src/test/java/org/olat/modules/dcompensation/manager/DisadvantageCompensationDAOTest.java +++ b/src/test/java/org/olat/modules/dcompensation/manager/DisadvantageCompensationDAOTest.java @@ -153,13 +153,35 @@ public class DisadvantageCompensationDAOTest extends OlatTestCase { List<String> subIdents = List.of(subIdent1, subIdent2); List<IdentityRef> disadvantegdIdentities = disadvantageCompensationDao .getActiveDisadvantagedUsers(entry, subIdents); - Assert.assertNotNull(disadvantegdIdentities); - Assert.assertEquals(2, disadvantegdIdentities.size()); assertThat(disadvantegdIdentities) + .isNotNull() + .hasSize(2) .extracting(IdentityRef::getKey) .containsExactlyInAnyOrder(identity1.getKey(), identity2.getKey()); } + @Test + public void isActiveDisadvantagedUser() { + Identity identity = JunitTestHelper.createAndPersistIdentityAsRndUser("dcompensation-16"); + Identity creator = JunitTestHelper.createAndPersistIdentityAsRndUser("dcompensation-18"); + RepositoryEntry entry = JunitTestHelper.createAndPersistRepositoryEntry(); + String subIdent = UUID.randomUUID().toString(); + Date approval = DateUtils.addDays(new Date(), -21); + + DisadvantageCompensation compensation = disadvantageCompensationDao + .createDisadvantageCompensation(identity, 15, "Not responsible", approval, creator, entry, subIdent, "Element-1"); + dbInstance.commitAndCloseSession(); + Assert.assertNotNull(compensation); + + boolean disadvantegdCompensations = disadvantageCompensationDao + .isActiveDisadvantagedUser(identity, entry, List.of(subIdent)); + Assert.assertTrue(disadvantegdCompensations); + + boolean creatorCompensations = disadvantageCompensationDao + .isActiveDisadvantagedUser(creator, entry, List.of(subIdent)); + Assert.assertFalse(creatorCompensations); + } + @Test public void getDisadvantageCompensationsByIdentityAndEntry() { Identity identity = JunitTestHelper.createAndPersistIdentityAsRndUser("dcompensation-9"); -- GitLab