From 9846c731a98cbc35b6418bcfbe67afe331a2c42f Mon Sep 17 00:00:00 2001 From: srosse <none@none> Date: Thu, 14 Jun 2018 21:17:03 +0200 Subject: [PATCH] OO-3241: implementation of an appeal workflow for absence management --- .../modules/lecture/LectureBlockAppeal.java | 35 +++ .../lecture/LectureBlockAppealStatus.java | 35 +++ .../modules/lecture/LectureBlockRollCall.java | 16 ++ .../LectureBlockRollCallSearchParameters.java | 24 ++ .../olat/modules/lecture/LectureService.java | 4 +- .../manager/LectureBlockRollCallDAO.java | 63 ++++- .../lecture/manager/LectureServiceImpl.java | 6 + .../model/LectureBlockAndRollCall.java | 18 ++ .../model/LectureBlockRollCallAndCoach.java | 55 ++++ .../model/LectureBlockRollCallImpl.java | 75 +++++- .../ui/AppealListRepositoryController.java | 238 +++++++++++++++++ .../ui/AppealListRepositoryDataModel.java | 241 ++++++++++++++++++ .../ui/AppealListRepositorySortDelegate.java | 39 +++ .../modules/lecture/ui/AppealRollCallRow.java | 61 +++++ .../lecture/ui/EditAppealController.java | 137 ++++++++++ .../ui/LectureRepositoryAdminController.java | 31 ++- .../ui/LectureSettingsAdminController.java | 5 +- .../ParticipantLectureBlocksController.java | 53 ++-- .../lecture/ui/_content/appeal_table.html | 2 + .../ui/_i18n/LocalStrings_de.properties | 10 + .../ui/_i18n/LocalStrings_en.properties | 10 + .../LectureBlockAppealStatusCellRenderer.java | 51 ++++ ...ectureBlockRollCallStatusCellRenderer.java | 43 +++- .../component/LecturesCompulsoryRenderer.java | 23 +- .../ui/export/LectureBlockAuditLogExport.java | 8 +- .../org/olat/upgrade/OLATUpgrade_13_0_0.java | 82 +++++- .../database/mysql/alter_12_4_x_to_13_0_0.sql | 5 + .../database/mysql/setupDatabase.sql | 3 + .../oracle/alter_12_4_x_to_13_0_0.sql | 8 + .../database/oracle/setupDatabase.sql | 3 + .../postgresql/alter_12_4_x_to_13_0_0.sql | 12 +- .../database/postgresql/setupDatabase.sql | 3 + 32 files changed, 1341 insertions(+), 58 deletions(-) create mode 100644 src/main/java/org/olat/modules/lecture/LectureBlockAppeal.java create mode 100644 src/main/java/org/olat/modules/lecture/LectureBlockAppealStatus.java create mode 100644 src/main/java/org/olat/modules/lecture/model/LectureBlockRollCallAndCoach.java create mode 100644 src/main/java/org/olat/modules/lecture/ui/AppealListRepositoryController.java create mode 100644 src/main/java/org/olat/modules/lecture/ui/AppealListRepositoryDataModel.java create mode 100644 src/main/java/org/olat/modules/lecture/ui/AppealListRepositorySortDelegate.java create mode 100644 src/main/java/org/olat/modules/lecture/ui/AppealRollCallRow.java create mode 100644 src/main/java/org/olat/modules/lecture/ui/EditAppealController.java create mode 100644 src/main/java/org/olat/modules/lecture/ui/_content/appeal_table.html create mode 100644 src/main/java/org/olat/modules/lecture/ui/component/LectureBlockAppealStatusCellRenderer.java diff --git a/src/main/java/org/olat/modules/lecture/LectureBlockAppeal.java b/src/main/java/org/olat/modules/lecture/LectureBlockAppeal.java new file mode 100644 index 00000000000..9e5cdf4b288 --- /dev/null +++ b/src/main/java/org/olat/modules/lecture/LectureBlockAppeal.java @@ -0,0 +1,35 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.lecture; + +import org.olat.core.id.CreateInfo; +import org.olat.core.id.ModifiedInfo; + +/** + * + * Initial date: 11 juin 2018<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public interface LectureBlockAppeal extends ModifiedInfo, CreateInfo { + + public Long getKey(); + +} diff --git a/src/main/java/org/olat/modules/lecture/LectureBlockAppealStatus.java b/src/main/java/org/olat/modules/lecture/LectureBlockAppealStatus.java new file mode 100644 index 00000000000..cc5cacb628b --- /dev/null +++ b/src/main/java/org/olat/modules/lecture/LectureBlockAppealStatus.java @@ -0,0 +1,35 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.lecture; + +/** + * + * Initial date: 11 juin 2018<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public enum LectureBlockAppealStatus { + + pending, + approved, + rejected, + oldWorkflow + +} diff --git a/src/main/java/org/olat/modules/lecture/LectureBlockRollCall.java b/src/main/java/org/olat/modules/lecture/LectureBlockRollCall.java index 9a3f03f9451..f2e3de093c4 100644 --- a/src/main/java/org/olat/modules/lecture/LectureBlockRollCall.java +++ b/src/main/java/org/olat/modules/lecture/LectureBlockRollCall.java @@ -57,6 +57,22 @@ public interface LectureBlockRollCall extends LectureBlockRollCallRef, ModifiedI public String getComment(); public void setComment(String comment); + + public Date getAppealDate(); + + public void setAppealDate(Date date); + + public LectureBlockAppealStatus getAppealStatus(); + + public void setAppealStatus(LectureBlockAppealStatus status); + + public String getAppealStatusReason(); + + public void setAppealStatusReason(String statusReason); + + public String getAppealReason(); + + public void setAppealReason(String reason); public Date getAbsenceSupervisorNotificationDate(); diff --git a/src/main/java/org/olat/modules/lecture/LectureBlockRollCallSearchParameters.java b/src/main/java/org/olat/modules/lecture/LectureBlockRollCallSearchParameters.java index 744841a6e77..96f51f1be72 100644 --- a/src/main/java/org/olat/modules/lecture/LectureBlockRollCallSearchParameters.java +++ b/src/main/java/org/olat/modules/lecture/LectureBlockRollCallSearchParameters.java @@ -19,6 +19,10 @@ */ package org.olat.modules.lecture; +import java.util.List; + +import org.olat.repository.RepositoryEntryRef; + /** * * Initial date: 28 août 2017<br> @@ -33,6 +37,9 @@ public class LectureBlockRollCallSearchParameters { private Long rollCallKey; private Long lectureBlockKey; + + private RepositoryEntryRef entry; + private List<LectureBlockAppealStatus> appealStatus; public Boolean getClosed() { return closed; @@ -73,4 +80,21 @@ public class LectureBlockRollCallSearchParameters { public void setLectureBlockKey(Long lectureBlockKey) { this.lectureBlockKey = lectureBlockKey; } + + public RepositoryEntryRef getEntry() { + return entry; + } + + public void setEntry(RepositoryEntryRef entry) { + this.entry = entry; + } + + public List<LectureBlockAppealStatus> getAppealStatus() { + return appealStatus; + } + + public void setAppealStatus(List<LectureBlockAppealStatus> appealStatus) { + this.appealStatus = appealStatus; + } + } diff --git a/src/main/java/org/olat/modules/lecture/LectureService.java b/src/main/java/org/olat/modules/lecture/LectureService.java index 8af6eaab96a..b1a42726816 100644 --- a/src/main/java/org/olat/modules/lecture/LectureService.java +++ b/src/main/java/org/olat/modules/lecture/LectureService.java @@ -28,6 +28,7 @@ import org.olat.core.id.Identity; import org.olat.modules.lecture.model.AggregatedLectureBlocksStatistics; import org.olat.modules.lecture.model.LectureBlockAndRollCall; import org.olat.modules.lecture.model.LectureBlockIdentityStatistics; +import org.olat.modules.lecture.model.LectureBlockRollCallAndCoach; import org.olat.modules.lecture.model.LectureBlockStatistics; import org.olat.modules.lecture.model.LectureBlockWithTeachers; import org.olat.modules.lecture.model.LectureStatisticsSearchParameters; @@ -290,9 +291,10 @@ public interface LectureService { public List<LectureBlockRollCall> getRollCalls(LectureBlockRef block); - public List<LectureBlockRollCall> getRollCalls(LectureBlockRollCallSearchParameters searchParams); + public List<LectureBlockRollCallAndCoach> getLectureBlockAndRollCalls(LectureBlockRollCallSearchParameters searchParams); + /** * Create a roll call with some settings. diff --git a/src/main/java/org/olat/modules/lecture/manager/LectureBlockRollCallDAO.java b/src/main/java/org/olat/modules/lecture/manager/LectureBlockRollCallDAO.java index 1cdb076a325..4726b744ffa 100644 --- a/src/main/java/org/olat/modules/lecture/manager/LectureBlockRollCallDAO.java +++ b/src/main/java/org/olat/modules/lecture/manager/LectureBlockRollCallDAO.java @@ -24,6 +24,7 @@ import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import javax.persistence.TemporalType; import javax.persistence.TypedQuery; @@ -35,6 +36,7 @@ import org.olat.core.commons.persistence.PersistenceHelper; import org.olat.core.id.Identity; import org.olat.core.util.StringHelper; import org.olat.modules.lecture.LectureBlock; +import org.olat.modules.lecture.LectureBlockAppealStatus; import org.olat.modules.lecture.LectureBlockAuditLog; import org.olat.modules.lecture.LectureBlockRef; import org.olat.modules.lecture.LectureBlockRollCall; @@ -45,6 +47,7 @@ import org.olat.modules.lecture.RepositoryEntryLectureConfiguration; import org.olat.modules.lecture.model.AggregatedLectureBlocksStatistics; import org.olat.modules.lecture.model.LectureBlockAndRollCall; import org.olat.modules.lecture.model.LectureBlockIdentityStatistics; +import org.olat.modules.lecture.model.LectureBlockRollCallAndCoach; import org.olat.modules.lecture.model.LectureBlockRollCallImpl; import org.olat.modules.lecture.model.LectureBlockStatistics; import org.olat.modules.lecture.model.LectureStatisticsSearchParameters; @@ -261,6 +264,47 @@ public class LectureBlockRollCallDAO { return rollCalls != null && rollCalls.size() > 0 ? rollCalls.get(0) : null; } + public List<LectureBlockRollCallAndCoach> getLectureBlockAndRollCalls(LectureBlockRollCallSearchParameters searchParams) { + List<LectureBlockRollCall> rollCalls = getRollCalls(searchParams); + Map<Long,String> coaches = getCoaches(searchParams.getEntry(), ", "); + List<LectureBlockRollCallAndCoach> blockAndRollCalls = new ArrayList<>(rollCalls.size()); + for(LectureBlockRollCall rollCall:rollCalls) { + LectureBlock block = rollCall.getLectureBlock(); + String coach = coaches.get(block.getKey()); + blockAndRollCalls.add(new LectureBlockRollCallAndCoach(coach, block, rollCall)); + } + return blockAndRollCalls; + } + + private Map<Long,String> getCoaches(RepositoryEntryRef entry, String teacherSeaparator) { + // append the coaches + StringBuilder sc = new StringBuilder(); + sc.append("select block.key, coach") + .append(" from lectureblock block") + .append(" inner join block.teacherGroup tGroup") + .append(" inner join tGroup.members membership") + .append(" inner join membership.identity coach") + .append(" inner join fetch coach.user usercoach") + .append(" where membership.role='").append("teacher").append("' and block.entry.key=:repoEntryKey"); + + //get all, it's quick + List<Object[]> rawCoachs = dbInstance.getCurrentEntityManager() + .createQuery(sc.toString(), Object[].class) + .setParameter("repoEntryKey", entry.getKey()) + .getResultList(); + Map<Long,String> coaches = new HashMap<>(); + for(Object[] rawCoach:rawCoachs) { + Long blockKey = (Long)rawCoach[0]; + Identity coach = (Identity)rawCoach[1]; + String fullname = userManager.getUserDisplayName(coach); + if(coaches.containsKey(blockKey)) { + fullname = coaches.get(blockKey) + " " + teacherSeaparator + " " + fullname; + } + coaches.put(blockKey, fullname); + } + return coaches; + } + public List<LectureBlockRollCall> getRollCalls(LectureBlockRollCallSearchParameters searchParams) { StringBuilder sb = new StringBuilder(); sb.append("select rollcall from lectureblockrollcall rollcall") @@ -304,10 +348,17 @@ public class LectureBlockRollCallDAO { where = PersistenceHelper.appendAnd(sb, where); sb.append("rollcall.key=:rollCallKey"); } - if(searchParams.getLectureBlockKey() != null) { where = PersistenceHelper.appendAnd(sb, where); - sb.append("rollcall.lectureBlock.key=:lectureBlockKey"); + sb.append("block.key=:lectureBlockKey"); + } + if(searchParams.getEntry() != null) { + where = PersistenceHelper.appendAnd(sb, where); + sb.append("block.entry.key=:entryKey"); + } + if(searchParams.getAppealStatus() != null && !searchParams.getAppealStatus().isEmpty()) { + where = PersistenceHelper.appendAnd(sb, where); + sb.append("rollcall.appealStatusString in (:appealStatus)"); } TypedQuery<LectureBlockRollCall> query = dbInstance.getCurrentEntityManager() @@ -318,6 +369,14 @@ public class LectureBlockRollCallDAO { if(searchParams.getLectureBlockKey() != null) { query.setParameter("lectureBlockKey", searchParams.getLectureBlockKey()); } + if(searchParams.getEntry() != null) { + query.setParameter("entryKey", searchParams.getEntry().getKey()); + } + if(searchParams.getAppealStatus() != null && !searchParams.getAppealStatus().isEmpty()) { + List<String> appealStatus = searchParams.getAppealStatus().stream() + .map(LectureBlockAppealStatus::name).collect(Collectors.toList()); + query.setParameter("appealStatus", appealStatus); + } return query.getResultList(); } diff --git a/src/main/java/org/olat/modules/lecture/manager/LectureServiceImpl.java b/src/main/java/org/olat/modules/lecture/manager/LectureServiceImpl.java index 692c85fba29..68484ece888 100644 --- a/src/main/java/org/olat/modules/lecture/manager/LectureServiceImpl.java +++ b/src/main/java/org/olat/modules/lecture/manager/LectureServiceImpl.java @@ -81,6 +81,7 @@ import org.olat.modules.lecture.model.AggregatedLectureBlocksStatistics; import org.olat.modules.lecture.model.LectureBlockAndRollCall; import org.olat.modules.lecture.model.LectureBlockIdentityStatistics; import org.olat.modules.lecture.model.LectureBlockImpl; +import org.olat.modules.lecture.model.LectureBlockRollCallAndCoach; import org.olat.modules.lecture.model.LectureBlockStatistics; import org.olat.modules.lecture.model.LectureBlockToTeacher; import org.olat.modules.lecture.model.LectureBlockWithTeachers; @@ -476,6 +477,11 @@ public class LectureServiceImpl implements LectureService, UserDataDeletable, De return lectureBlockRollCallDao.getRollCalls(searchParams); } + @Override + public List<LectureBlockRollCallAndCoach> getLectureBlockAndRollCalls(LectureBlockRollCallSearchParameters searchParams) { + return lectureBlockRollCallDao.getLectureBlockAndRollCalls(searchParams); + } + @Override public LectureBlockRollCall getOrCreateRollCall(Identity identity, LectureBlock lectureBlock, Boolean authorizedAbsence, String reasonAbsence) { diff --git a/src/main/java/org/olat/modules/lecture/model/LectureBlockAndRollCall.java b/src/main/java/org/olat/modules/lecture/model/LectureBlockAndRollCall.java index b21e6801d5b..de9cd771aa1 100644 --- a/src/main/java/org/olat/modules/lecture/model/LectureBlockAndRollCall.java +++ b/src/main/java/org/olat/modules/lecture/model/LectureBlockAndRollCall.java @@ -22,6 +22,7 @@ package org.olat.modules.lecture.model; import java.util.Date; import org.olat.modules.lecture.LectureBlock; +import org.olat.modules.lecture.LectureBlockAppealStatus; import org.olat.modules.lecture.LectureBlockRef; import org.olat.modules.lecture.LectureBlockRollCall; import org.olat.modules.lecture.LectureBlockRollCallRef; @@ -52,6 +53,9 @@ public class LectureBlockAndRollCall { private final int lecturesAttendedNumber; private final Boolean lecturesAuthorizedAbsent; + private final Date appealDate; + private final LectureBlockAppealStatus appealStatus; + private String coach; public LectureBlockAndRollCall(String entryDisplayname, LectureBlock lectureBlock, LectureBlockRollCall rollCall) { @@ -71,11 +75,15 @@ public class LectureBlockAndRollCall { lecturesAttendedNumber = 0; lecturesAbsentNumber = 0; lecturesAuthorizedAbsent = null; + appealDate = null; + appealStatus = null; } else { rollCallKey = rollCall.getKey(); lecturesAttendedNumber = rollCall.getLecturesAttendedNumber(); lecturesAbsentNumber = rollCall.getLecturesAbsentNumber(); lecturesAuthorizedAbsent = rollCall.getAbsenceAuthorized(); + appealDate = rollCall.getAppealDate(); + appealStatus = rollCall.getAppealStatus(); } } @@ -142,4 +150,14 @@ public class LectureBlockAndRollCall { public void setCoach(String coach) { this.coach = coach; } + + public Date getAppealDate() { + return appealDate; + } + + public LectureBlockAppealStatus getAppealStatus() { + return appealStatus; + } + + } diff --git a/src/main/java/org/olat/modules/lecture/model/LectureBlockRollCallAndCoach.java b/src/main/java/org/olat/modules/lecture/model/LectureBlockRollCallAndCoach.java new file mode 100644 index 00000000000..c83c1ec806b --- /dev/null +++ b/src/main/java/org/olat/modules/lecture/model/LectureBlockRollCallAndCoach.java @@ -0,0 +1,55 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.lecture.model; + +import org.olat.modules.lecture.LectureBlock; +import org.olat.modules.lecture.LectureBlockRollCall; + +/** + * + * Initial date: 13 juin 2018<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class LectureBlockRollCallAndCoach { + + private String coach; + private final LectureBlock block; + private final LectureBlockRollCall rollCall; + + public LectureBlockRollCallAndCoach(String coach, LectureBlock block, LectureBlockRollCall rollCall) { + this.coach = coach; + this.block = block; + this.rollCall = rollCall; + } + + public LectureBlock getLectureBlock() { + return block; + } + + public LectureBlockRollCall getRollCall() { + return rollCall; + } + + public String getCoach() { + return coach; + } + +} diff --git a/src/main/java/org/olat/modules/lecture/model/LectureBlockRollCallImpl.java b/src/main/java/org/olat/modules/lecture/model/LectureBlockRollCallImpl.java index 58ff369f596..dd5afec4471 100644 --- a/src/main/java/org/olat/modules/lecture/model/LectureBlockRollCallImpl.java +++ b/src/main/java/org/olat/modules/lecture/model/LectureBlockRollCallImpl.java @@ -42,6 +42,7 @@ import org.olat.core.id.Identity; import org.olat.core.id.Persistable; import org.olat.core.util.StringHelper; import org.olat.modules.lecture.LectureBlock; +import org.olat.modules.lecture.LectureBlockAppealStatus; import org.olat.modules.lecture.LectureBlockRollCall; /** @@ -85,12 +86,19 @@ public class LectureBlockRollCallImpl implements Persistable, LectureBlockRollCa @Column(name="l_absence_authorized", nullable=true, insertable=true, updatable=true) private Boolean absenceAuthorized; @Temporal(TemporalType.TIMESTAMP) - @Column(name="l_absence_appeal_date", nullable=true, insertable=true, updatable=true) - private Date absenceAppealDate; - @Temporal(TemporalType.TIMESTAMP) @Column(name="l_absence_supervisor_noti_date", nullable=true, insertable=true, updatable=true) private Date absenceSupervisorNotificationDate; + @Temporal(TemporalType.TIMESTAMP) + @Column(name="l_absence_appeal_date", nullable=true, insertable=true, updatable=true) + private Date appealDate; + @Column(name="l_appeal_reason", nullable=true, insertable=true, updatable=true) + private String appealReason; + @Column(name="l_appeal_status", nullable=true, insertable=true, updatable=true) + private String appealStatusString; + @Column(name="l_appeal_status_reason", nullable=true, insertable=true, updatable=true) + private String statusReason; + @ManyToOne(targetEntity=IdentityImpl.class,fetch=FetchType.LAZY,optional=false) @JoinColumn(name="fk_identity", nullable=false, insertable=true, updatable=false) private Identity identity; @@ -152,14 +160,6 @@ public class LectureBlockRollCallImpl implements Persistable, LectureBlockRollCa this.absenceAuthorized = absenceAuthorized; } - public Date getAbsenceAppealDate() { - return absenceAppealDate; - } - - public void setAbsenceAppealDate(Date absenceAppealDate) { - this.absenceAppealDate = absenceAppealDate; - } - @Override public Identity getIdentity() { return identity; @@ -258,6 +258,59 @@ public class LectureBlockRollCallImpl implements Persistable, LectureBlockRollCa public void setAbsenceSupervisorNotificationDate(Date absenceSupervisorNotificationDate) { this.absenceSupervisorNotificationDate = absenceSupervisorNotificationDate; } + + @Override + public Date getAppealDate() { + return appealDate; + } + + @Override + public void setAppealDate(Date appealDate) { + this.appealDate = appealDate; + } + + public String getAppealStatusString() { + return appealStatusString; + } + + public void setAppealStatusString(String statusString) { + this.appealStatusString = statusString; + } + + @Override + public LectureBlockAppealStatus getAppealStatus() { + return StringHelper.containsNonWhitespace(appealStatusString) + ? LectureBlockAppealStatus.valueOf(appealStatusString) : null; + } + + @Override + public void setAppealStatus(LectureBlockAppealStatus appealStatus) { + if(appealStatus == null) { + appealStatusString = null; + } else { + appealStatusString = appealStatus.name(); + } + } + + @Override + public String getAppealReason() { + return appealReason; + } + + @Override + public void setAppealReason(String appealReason) { + this.appealReason = appealReason; + } + + @Override + public String getAppealStatusReason() { + return statusReason; + } + + @Override + public void setAppealStatusReason(String statusReason) { + this.statusReason = statusReason; + } @Override public LectureBlock getLectureBlock() { diff --git a/src/main/java/org/olat/modules/lecture/ui/AppealListRepositoryController.java b/src/main/java/org/olat/modules/lecture/ui/AppealListRepositoryController.java new file mode 100644 index 00000000000..ae32c92df87 --- /dev/null +++ b/src/main/java/org/olat/modules/lecture/ui/AppealListRepositoryController.java @@ -0,0 +1,238 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.lecture.ui; + +import java.util.ArrayList; +import java.util.List; + +import org.olat.basesecurity.BaseSecurityModule; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItem; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.FlexiTableElement; +import org.olat.core.gui.components.form.flexible.elements.FlexiTableFilter; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormEvent; +import org.olat.core.gui.components.form.flexible.impl.elements.table.DateFlexiCellRenderer; +import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiColumnModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiColumnModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableDataModelFactory; +import org.olat.core.gui.components.form.flexible.impl.elements.table.SelectionEvent; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController; +import org.olat.core.id.Roles; +import org.olat.modules.lecture.LectureBlockAppealStatus; +import org.olat.modules.lecture.LectureBlockRollCall; +import org.olat.modules.lecture.LectureBlockRollCallSearchParameters; +import org.olat.modules.lecture.LectureModule; +import org.olat.modules.lecture.LectureService; +import org.olat.modules.lecture.model.LectureBlockRollCallAndCoach; +import org.olat.modules.lecture.ui.AppealListRepositoryDataModel.AppealCols; +import org.olat.modules.lecture.ui.component.LectureBlockAppealStatusCellRenderer; +import org.olat.modules.lecture.ui.component.LectureBlockRollCallStatusCellRenderer; +import org.olat.modules.lecture.ui.component.LecturesCompulsoryRenderer; +import org.olat.repository.RepositoryEntry; +import org.olat.user.UserManager; +import org.olat.user.propertyhandlers.UserPropertyHandler; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 12 juin 2018<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class AppealListRepositoryController extends FormBasicController { + + protected static final String USER_PROPS_ID = TeacherRollCallController.USER_PROPS_ID; + protected static final int USER_PROPS_OFFSET = 500; + + private FlexiTableElement tableEl; + private AppealListRepositoryDataModel tableModel; + + private CloseableModalController cmc; + private EditAppealController appealCtrl; + + private final boolean authorizedAbsenceEnabled; + private final boolean absenceDefaultAuthorized; + + private final RepositoryEntry entry; + private final boolean isAdministrativeUser; + private List<UserPropertyHandler> userPropertyHandlers; + + @Autowired + private UserManager userManager; + @Autowired + private LectureModule lectureModule; + @Autowired + private LectureService lectureService; + @Autowired + private BaseSecurityModule securityModule; + + public AppealListRepositoryController(UserRequest ureq, WindowControl wControl, RepositoryEntry entry) { + super(ureq, wControl, "appeal_table"); + this.entry = entry; + setTranslator(userManager.getPropertyHandlerTranslator(getTranslator())); + + Roles roles = ureq.getUserSession().getRoles(); + isAdministrativeUser = securityModule.isUserAllowedAdminProps(roles); + userPropertyHandlers = userManager.getUserPropertyHandlersFor(USER_PROPS_ID, isAdministrativeUser); + + authorizedAbsenceEnabled = lectureModule.isAuthorizedAbsenceEnabled(); + absenceDefaultAuthorized = lectureModule.isAbsenceDefaultAuthorized(); + + initForm(ureq); + loadModel(true); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + + FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel(); + if(isAdministrativeUser) { + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(AppealCols.username)); + } + + int colPos = USER_PROPS_OFFSET; + for (UserPropertyHandler userPropertyHandler : userPropertyHandlers) { + if (userPropertyHandler == null) continue; + + String propName = userPropertyHandler.getName(); + boolean visible = userManager.isMandatoryUserProperty(USER_PROPS_ID , userPropertyHandler); + + FlexiColumnModel col = new DefaultFlexiColumnModel(visible, userPropertyHandler.i18nColumnDescriptorLabelKey(), colPos, true, propName); + columnsModel.addFlexiColumnModel(col); + colPos++; + } + + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(AppealCols.lectureBlockDate, new DateFlexiCellRenderer(getLocale()))); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(AppealCols.lectureBlockName)); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(AppealCols.coach)); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(AppealCols.plannedLectures, new LecturesCompulsoryRenderer())); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(AppealCols.attendedLectures)); + if(authorizedAbsenceEnabled) { + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(AppealCols.unauthorizedAbsentLectures)); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(AppealCols.authorizedAbsentLectures)); + } else { + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(AppealCols.absentLectures)); + } + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(AppealCols.lectureBlockStatus, + new LectureBlockRollCallStatusCellRenderer(authorizedAbsenceEnabled, absenceDefaultAuthorized, getTranslator()))); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(AppealCols.appealStatus, + new LectureBlockAppealStatusCellRenderer(getTranslator()))); + DefaultFlexiColumnModel editColumn = new DefaultFlexiColumnModel("table.header.edit", translate("table.header.edit"), "edit"); + editColumn.setExportable(false); + columnsModel.addFlexiColumnModel(editColumn); + + tableModel = new AppealListRepositoryDataModel(columnsModel,authorizedAbsenceEnabled, absenceDefaultAuthorized, getLocale()); + tableEl = uifactory.addTableElement(getWindowControl(), "table", tableModel, 20, false, getTranslator(), formLayout); + tableEl.setCustomizeColumns(true); + + List<FlexiTableFilter> filters = new ArrayList<>(16); + filters.add(new FlexiTableFilter(translate("appeal.".concat(LectureBlockAppealStatus.pending.name())), + LectureBlockAppealStatus.pending.name())); + filters.add(new FlexiTableFilter(translate("appeal.".concat(LectureBlockAppealStatus.approved.name())), + LectureBlockAppealStatus.approved.name())); + filters.add(new FlexiTableFilter(translate("appeal.".concat(LectureBlockAppealStatus.rejected.name())), + LectureBlockAppealStatus.rejected.name())); + tableEl.setFilters("filer", filters, true); + tableEl.setExportEnabled(true); + tableEl.setAndLoadPersistedPreferences(ureq, "appeal-roll-call"); + } + + private void loadModel(boolean reset) { + LectureBlockRollCallSearchParameters params = new LectureBlockRollCallSearchParameters(); + params.setEntry(entry); + List<LectureBlockAppealStatus> status = new ArrayList<>(); + status.add(LectureBlockAppealStatus.pending); + status.add(LectureBlockAppealStatus.approved); + status.add(LectureBlockAppealStatus.rejected); + params.setAppealStatus(status); + + List<LectureBlockRollCallAndCoach> rollCallsWithCoach = lectureService.getLectureBlockAndRollCalls(params); + List<AppealRollCallRow> rows = new ArrayList<>(rollCallsWithCoach.size()); + for(LectureBlockRollCallAndCoach rollCallWithCoach:rollCallsWithCoach) { + rows.add(new AppealRollCallRow(rollCallWithCoach, userPropertyHandlers, getLocale())); + } + tableModel.setObjects(rows); + tableEl.reset(reset, reset, true); + } + + @Override + protected void doDispose() { + // + } + + @Override + protected void event(UserRequest ureq, Controller source, Event event) { + if(appealCtrl == source) { + if(event == Event.DONE_EVENT || event == Event.CHANGED_EVENT) { + loadModel(false); + } + cmc.deactivate(); + cleanUp(); + } else if(cmc == source) { + cleanUp(); + } + super.event(ureq, source, event); + } + + private void cleanUp() { + removeAsListenerAndDispose(appealCtrl); + removeAsListenerAndDispose(cmc); + appealCtrl = null; + cmc = null; + } + + @Override + protected void formOK(UserRequest ureq) { + // + } + + @Override + protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { + if(source == tableEl) { + if(event instanceof SelectionEvent) { + SelectionEvent se = (SelectionEvent)event; + String cmd = se.getCommand(); + AppealRollCallRow row = tableModel.getObject(se.getIndex()); + if("edit".equals(cmd)) { + doEditAppeal(ureq, row.getRollCall()); + } + } + } + super.formInnerEvent(ureq, source, event); + } + + private void doEditAppeal(UserRequest ureq, LectureBlockRollCall rollCall) { + if(appealCtrl != null) return; + + appealCtrl = new EditAppealController(ureq, getWindowControl(), rollCall); + listenTo(appealCtrl); + + String title = translate("appeal.title", new String[]{ rollCall.getLectureBlock().getTitle() }); + cmc = new CloseableModalController(getWindowControl(), "close", appealCtrl.getInitialComponent(), true, title); + listenTo(cmc); + cmc.activate(); + } +} diff --git a/src/main/java/org/olat/modules/lecture/ui/AppealListRepositoryDataModel.java b/src/main/java/org/olat/modules/lecture/ui/AppealListRepositoryDataModel.java new file mode 100644 index 00000000000..fc01511d9ac --- /dev/null +++ b/src/main/java/org/olat/modules/lecture/ui/AppealListRepositoryDataModel.java @@ -0,0 +1,241 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.lecture.ui; + +import java.util.List; +import java.util.Locale; +import java.util.stream.Collectors; + +import org.olat.core.commons.persistence.SortKey; +import org.olat.core.gui.components.form.flexible.elements.FlexiTableFilter; +import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiTableDataModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FilterableFlexiTableModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiSortableColumnDef; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.SortableFlexiTableDataModel; +import org.olat.modules.lecture.LectureBlockAppealStatus; +import org.olat.modules.lecture.LectureBlockStatus; +import org.olat.modules.lecture.LectureRollCallStatus; +import org.olat.modules.lecture.model.LectureBlockAndRollCall; + +/** + * + * Initial date: 13 juin 2018<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class AppealListRepositoryDataModel extends DefaultFlexiTableDataModel<AppealRollCallRow> +implements SortableFlexiTableDataModel<AppealRollCallRow>, FilterableFlexiTableModel { + + private final Locale locale; + private final boolean authorizedAbsenceEnabled; + private final boolean absenceDefaultAuthorized; + + private List<AppealRollCallRow> backups; + + public AppealListRepositoryDataModel(FlexiTableColumnModel columnModel, + boolean authorizedAbsenceEnabled, boolean absenceDefaultAuthorized, Locale locale) { + super(columnModel); + this.locale = locale; + this.authorizedAbsenceEnabled = authorizedAbsenceEnabled; + this.absenceDefaultAuthorized = absenceDefaultAuthorized; + } + + @Override + public void sort(SortKey orderBy) { + if(orderBy != null) { + List<AppealRollCallRow> rows = new AppealListRepositorySortDelegate(orderBy, this, locale).sort(); + super.setObjects(rows); + } + } + + @Override + public void filter(List<FlexiTableFilter> filters) { + if(filters != null && !filters.isEmpty() && filters.get(0) != null) { + List<AppealRollCallRow> filteredRows = backups.stream() + .filter(row -> accept(row, filters)) + .collect(Collectors.toList()); + super.setObjects(filteredRows); + } else { + super.setObjects(backups); + } + } + + private boolean accept(AppealRollCallRow row, List<FlexiTableFilter> filters) { + boolean accepted = false; + LectureBlockAppealStatus status = row.getRollCall().getAppealStatus(); + if(status != null) { + for(FlexiTableFilter filter:filters) { + if(status.name().equals(filter.getFilter())) { + accepted = true; + } + } + } + return accepted; + } + + @Override + public Object getValueAt(int row, int col) { + AppealRollCallRow rollCallRow = getObject(row); + return getValueAt(rollCallRow, col); + } + + @Override + public Object getValueAt(AppealRollCallRow row, int col) { + if(col < AppealListRepositoryController.USER_PROPS_OFFSET) { + switch(AppealCols.values()[col]) { + case username: return row.getIdentityName(); + case lectureBlockDate: return row.getRollCall().getLectureBlock().getStartDate(); + case lectureBlockName: return row.getRollCall().getLectureBlock().getTitle(); + case coach: return row.getCoach(); + case plannedLectures: { + if(LectureBlockStatus.cancelled.equals(row.getLectureBlockAndRollCall().getStatus())) { + return null; + } + return row.getLectureBlockAndRollCall().getPlannedLecturesNumber(); + } + case attendedLectures: { + if(!isDataVisible(row.getLectureBlockAndRollCall())) { + return null; + } + if(row.getLectureBlockAndRollCall().isCompulsory()) { + return positive(row.getLectureBlockAndRollCall().getLecturesAttendedNumber()); + } + return null; + } + case unauthorizedAbsentLectures: + case absentLectures: { + if(!isDataVisible(row.getLectureBlockAndRollCall())) { + return null; + } + if(row.getLectureBlockAndRollCall().isCompulsory()) { + long value; + if(isAuthorized(row.getLectureBlockAndRollCall())) { + value = 0l; + } else { + value = positive(row.getLectureBlockAndRollCall().getLecturesAbsentNumber()); + } + return value; + } + return null; + } + case authorizedAbsentLectures: { + if(!isDataVisible(row.getLectureBlockAndRollCall())) { + return null; + } + if(row.getLectureBlockAndRollCall().isCompulsory()) { + long value; + if(isAuthorized(row.getLectureBlockAndRollCall())) { + value = positive(row.getLectureBlockAndRollCall().getLecturesAbsentNumber()); + } else { + value = 0l; + } + return value; + } + return null; + } + case lectureBlockStatus: return row; + case appealStatus: return row.getRollCall().getAppealStatus(); + default: return null; + } + } + + int propPos = col - TeacherRollCallController.USER_PROPS_OFFSET; + return row.getIdentityProp(propPos); + } + + private boolean isAuthorized(LectureBlockAndRollCall row) { + boolean authorized; + if(authorizedAbsenceEnabled) { + if((row.getLecturesAuthorizedAbsent() == null && absenceDefaultAuthorized) + || (row.getLecturesAuthorizedAbsent() != null && row.getLecturesAuthorizedAbsent().booleanValue())) { + authorized = true; + } else { + authorized = false; + } + } else { + authorized = false; + } + return authorized; + } + + private boolean isDataVisible(LectureBlockAndRollCall row) { + LectureBlockStatus status = row.getStatus(); + if(LectureBlockStatus.cancelled.equals(status)) { + return false; + } + LectureRollCallStatus rollCallStatus = row.getRollCallStatus(); + return status == LectureBlockStatus.done + && (rollCallStatus == LectureRollCallStatus.closed || rollCallStatus == LectureRollCallStatus.autoclosed); + } + + private int positive(int num) { + return num < 0 ? 0 : num; + } + + @Override + public void setObjects(List<AppealRollCallRow> objects) { + super.setObjects(objects); + backups = objects; + } + + @Override + public DefaultFlexiTableDataModel<AppealRollCallRow> createCopyWithEmptyList() { + return new AppealListRepositoryDataModel(getTableColumnModel(), + authorizedAbsenceEnabled, absenceDefaultAuthorized, locale); + } + + public enum AppealCols implements FlexiSortableColumnDef { + username("table.header.username"), + lectureBlockDate("table.header.date"), + lectureBlockName("table.header.lecture.block"), + coach("table.header.teachers"), + plannedLectures("table.header.planned.lectures"), + attendedLectures("table.header.attended.lectures"), + absentLectures("table.header.absent.lectures"), + unauthorizedAbsentLectures("table.header.unauthorized.absence"), + authorizedAbsentLectures("table.header.authorized.absence"), + lectureBlockStatus("table.header.status"), + appealStatus("table.header.appeal.status"); + + private final String i18nKey; + + private AppealCols(String i18nKey) { + this.i18nKey = i18nKey; + } + + @Override + public String i18nHeaderKey() { + return i18nKey; + } + + @Override + public boolean sortable() { + return true; + } + + @Override + public String sortKey() { + return name(); + } + + } + +} diff --git a/src/main/java/org/olat/modules/lecture/ui/AppealListRepositorySortDelegate.java b/src/main/java/org/olat/modules/lecture/ui/AppealListRepositorySortDelegate.java new file mode 100644 index 00000000000..f67291dcd34 --- /dev/null +++ b/src/main/java/org/olat/modules/lecture/ui/AppealListRepositorySortDelegate.java @@ -0,0 +1,39 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.lecture.ui; + +import java.util.Locale; + +import org.olat.core.commons.persistence.SortKey; +import org.olat.core.gui.components.form.flexible.impl.elements.table.SortableFlexiTableModelDelegate; + +/** + * + * Initial date: 14 juin 2018<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class AppealListRepositorySortDelegate extends SortableFlexiTableModelDelegate<AppealRollCallRow> { + + public AppealListRepositorySortDelegate(SortKey orderBy, AppealListRepositoryDataModel tableModel, Locale locale) { + super(orderBy, tableModel, locale); + } + +} \ No newline at end of file diff --git a/src/main/java/org/olat/modules/lecture/ui/AppealRollCallRow.java b/src/main/java/org/olat/modules/lecture/ui/AppealRollCallRow.java new file mode 100644 index 00000000000..796d0d1cbb4 --- /dev/null +++ b/src/main/java/org/olat/modules/lecture/ui/AppealRollCallRow.java @@ -0,0 +1,61 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.lecture.ui; + +import java.util.List; +import java.util.Locale; + +import org.olat.modules.lecture.LectureBlockRollCall; +import org.olat.modules.lecture.model.LectureBlockAndRollCall; +import org.olat.modules.lecture.model.LectureBlockRollCallAndCoach; +import org.olat.user.UserPropertiesRow; +import org.olat.user.propertyhandlers.UserPropertyHandler; + +/** + * + * Initial date: 13 juin 2018<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class AppealRollCallRow extends UserPropertiesRow { + + private final LectureBlockRollCallAndCoach rollCall; + private final LectureBlockAndRollCall lectureBlockAndRollCall; + + public AppealRollCallRow(LectureBlockRollCallAndCoach rollCall, List<UserPropertyHandler> propertyHandlers, Locale locale) { + super(rollCall.getRollCall().getIdentity(), propertyHandlers, locale); + this.rollCall = rollCall; + lectureBlockAndRollCall = new LectureBlockAndRollCall("", rollCall.getLectureBlock(), rollCall.getRollCall()); + } + + public String getCoach() { + return rollCall.getCoach(); + } + + public LectureBlockRollCall getRollCall() { + return rollCall.getRollCall(); + } + + public LectureBlockAndRollCall getLectureBlockAndRollCall() { + return lectureBlockAndRollCall; + } + + +} diff --git a/src/main/java/org/olat/modules/lecture/ui/EditAppealController.java b/src/main/java/org/olat/modules/lecture/ui/EditAppealController.java new file mode 100644 index 00000000000..9e5eabefaad --- /dev/null +++ b/src/main/java/org/olat/modules/lecture/ui/EditAppealController.java @@ -0,0 +1,137 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.lecture.ui; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.SingleSelection; +import org.olat.core.gui.components.form.flexible.elements.TextElement; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.id.Identity; +import org.olat.core.util.StringHelper; +import org.olat.modules.lecture.LectureBlock; +import org.olat.modules.lecture.LectureBlockAppealStatus; +import org.olat.modules.lecture.LectureBlockAuditLog; +import org.olat.modules.lecture.LectureBlockRollCall; +import org.olat.modules.lecture.LectureService; +import org.olat.repository.RepositoryEntry; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 13 juin 2018<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class EditAppealController extends FormBasicController { + + private TextElement reasonEl; + private SingleSelection statusEl; + + private LectureBlockRollCall rollCall; + + @Autowired + private LectureService lectureService; + + public EditAppealController(UserRequest ureq, WindowControl wControl, LectureBlockRollCall rollCall) { + super(ureq, wControl); + this.rollCall = rollCall; + + initForm(ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + setFormDescription("appeal.form.explain"); + + String reason = rollCall.getAppealStatusReason(); + reasonEl = uifactory.addTextAreaElement("reason", 12, 60, reason, formLayout); + + String[] statusKeys = new String[] { + LectureBlockAppealStatus.pending.name(), + LectureBlockAppealStatus.approved.name(), + LectureBlockAppealStatus.rejected.name(), + }; + String[] statusValues = new String[] { + translate("appeal.pending"), translate("appeal.approved"), translate("appeal.rejected") + }; + statusEl = uifactory.addRadiosVertical("appeal.status", "appeal.status", formLayout, statusKeys, statusValues); + if(rollCall.getAppealStatus() != null) { + statusEl.select(rollCall.getAppealStatus().name(), true); + } else { + statusEl.select(statusKeys[0], true); + } + + FormLayoutContainer buttonsCont = FormLayoutContainer.createButtonLayout("buttons", getTranslator()); + formLayout.add(buttonsCont); + uifactory.addFormCancelButton("cancel", buttonsCont, ureq, getWindowControl()); + uifactory.addFormSubmitButton("save", buttonsCont); + } + + @Override + protected void doDispose() { + // + } + + @Override + protected boolean validateFormLogic(UserRequest ureq) { + boolean allOk = super.validateFormLogic(ureq); + + statusEl.clearError(); + reasonEl.clearError(); + if(!statusEl.isOneSelected()) { + statusEl.setErrorKey("form.legende.mandatory", null); + allOk &= false; + } else if(LectureBlockAppealStatus.pending.name().equals(statusEl.getSelectedKey()) + && !StringHelper.containsNonWhitespace(reasonEl.getValue())) { + reasonEl.setErrorKey("form.legende.mandatory", null); + allOk &= false; + } + + return allOk; + } + + @Override + protected void formOK(UserRequest ureq) { + rollCall = lectureService.getRollCall(rollCall); + LectureBlock lectureBlock = rollCall.getLectureBlock(); + Identity assessedIdentity = rollCall.getIdentity(); + RepositoryEntry entry = lectureBlock.getEntry(); + String before = lectureService.toAuditXml(rollCall); + + rollCall.setAppealStatusReason(reasonEl.getValue()); + rollCall.setAppealStatus(LectureBlockAppealStatus.valueOf(statusEl.getSelectedKey())); + rollCall = lectureService.updateRollCall(rollCall); + + lectureService.auditLog(LectureBlockAuditLog.Action.updateRollCall, before, lectureService.toAuditXml(rollCall), + reasonEl.getValue(), lectureBlock, rollCall, entry, assessedIdentity, getIdentity()); + + fireEvent(ureq, Event.DONE_EVENT); + } + + @Override + protected void formCancelled(UserRequest ureq) { + fireEvent(ureq, Event.CANCELLED_EVENT); + } +} diff --git a/src/main/java/org/olat/modules/lecture/ui/LectureRepositoryAdminController.java b/src/main/java/org/olat/modules/lecture/ui/LectureRepositoryAdminController.java index 64726cdca0f..3a95ee690a5 100644 --- a/src/main/java/org/olat/modules/lecture/ui/LectureRepositoryAdminController.java +++ b/src/main/java/org/olat/modules/lecture/ui/LectureRepositoryAdminController.java @@ -63,8 +63,9 @@ public class LectureRepositoryAdminController extends BasicController implements private final VelocityContainer mainVC; private final SegmentViewComponent segmentView; private final TooledStackedPanel stackPanel; - private final Link lecturesLink, settingsLink, participantsLink; + private final Link lecturesLink, appealsLink, settingsLink, participantsLink; + private AppealListRepositoryController appealsCtrl; private LectureListRepositoryController lecturesCtrl; private final LectureRepositorySettingsController settingsCtrl; private ParticipantListRepositoryController participantsCtrl; @@ -98,6 +99,7 @@ public class LectureRepositoryAdminController extends BasicController implements lecturesLink = LinkFactory.createLink("repo.lectures.block", mainVC, this); participantsLink = LinkFactory.createLink("repo.participants", mainVC, this); + appealsLink = LinkFactory.createLink("repo.lectures.appeals", mainVC, this); settingsLink = LinkFactory.createLink("repo.settings", mainVC, this); WindowControl swControl = addToHistory(ureq, OresHelper.createOLATResourceableType("Settings"), null); @@ -107,6 +109,9 @@ public class LectureRepositoryAdminController extends BasicController implements if(settingsCtrl.isLectureEnabled()) { segmentView.addSegment(lecturesLink, true); segmentView.addSegment(participantsLink, false); + if(lectureModule.isAbsenceAppealEnabled()) { + segmentView.addSegment(appealsLink, false); + } doOpenLectures(ureq); } else { doOpenSettings(ureq); @@ -156,6 +161,11 @@ public class LectureRepositoryAdminController extends BasicController implements } else if("Settings".equalsIgnoreCase(name)) { doOpenSettings(ureq); segmentView.select(settingsLink); + } else if("Appeals".equalsIgnoreCase(name)) { + if(lectureModule.isAbsenceAppealEnabled()) { + doOpenAppeals(ureq); + segmentView.select(appealsLink); + } } } @@ -172,6 +182,8 @@ public class LectureRepositoryAdminController extends BasicController implements doOpenSettings(ureq); } else if(clickedLink == participantsLink) { doOpenParticipants(ureq); + } else if(clickedLink == appealsLink) { + doOpenAppeals(ureq); } } } else if(archiveLink == source) { @@ -197,6 +209,9 @@ public class LectureRepositoryAdminController extends BasicController implements private void updateSegments() { if(settingsCtrl.isLectureEnabled()) { if(segmentView.getSegments().size() == 1) { + if(lectureModule.isAbsenceAppealEnabled()) { + segmentView.addSegment(0, appealsLink, false); + } segmentView.addSegment(0, participantsLink, false); segmentView.addSegment(0, lecturesLink, false); } @@ -204,6 +219,7 @@ public class LectureRepositoryAdminController extends BasicController implements // remove the unused segments segmentView.removeSegment(lecturesLink); segmentView.removeSegment(participantsLink); + segmentView.removeSegment(appealsLink); } } @@ -236,8 +252,19 @@ public class LectureRepositoryAdminController extends BasicController implements mainVC.put("segmentCmp", participantsCtrl.getInitialComponent()); } + private void doOpenAppeals(UserRequest ureq) { + if(appealsCtrl == null) { + OLATResourceable ores = OresHelper.createOLATResourceableType("Appeals"); + WindowControl swControl = addToHistory(ureq, ores, null); + appealsCtrl = new AppealListRepositoryController(ureq, swControl, entry); + listenTo(appealsCtrl); + } else { + addToHistory(ureq, appealsCtrl); + } + mainVC.put("segmentCmp", appealsCtrl.getInitialComponent()); + } + private void doExportArchive(UserRequest ureq) { - LecturesBlocksEntryExport archive = new LecturesBlocksEntryExport(entry, isAdministrativeUser, authorizedAbsenceEnabled, getTranslator()); ureq.getDispatchResult().setResultingMediaResource(archive); } diff --git a/src/main/java/org/olat/modules/lecture/ui/LectureSettingsAdminController.java b/src/main/java/org/olat/modules/lecture/ui/LectureSettingsAdminController.java index 6ad9a4b2e27..9f8448398ad 100644 --- a/src/main/java/org/olat/modules/lecture/ui/LectureSettingsAdminController.java +++ b/src/main/java/org/olat/modules/lecture/ui/LectureSettingsAdminController.java @@ -283,10 +283,9 @@ public class LectureSettingsAdminController extends FormBasicController { // } - @Override protected boolean validateFormLogic(UserRequest ureq) { - boolean allOk = true; + boolean allOk = super.validateFormLogic(ureq); attendanceRateEl.clearError(); if(StringHelper.containsNonWhitespace(attendanceRateEl.getValue())) { @@ -317,7 +316,7 @@ public class LectureSettingsAdminController extends FormBasicController { allOk &= validateInt(reminderPeriodEl); } - return allOk & super.validateFormLogic(ureq); + return allOk; } private boolean validateInt(TextElement el) { diff --git a/src/main/java/org/olat/modules/lecture/ui/ParticipantLectureBlocksController.java b/src/main/java/org/olat/modules/lecture/ui/ParticipantLectureBlocksController.java index 3cf7745d03a..fea2663c914 100644 --- a/src/main/java/org/olat/modules/lecture/ui/ParticipantLectureBlocksController.java +++ b/src/main/java/org/olat/modules/lecture/ui/ParticipantLectureBlocksController.java @@ -22,9 +22,7 @@ package org.olat.modules.lecture.ui; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; -import java.util.HashMap; import java.util.List; -import java.util.Map; import org.olat.NewControllerFactory; import org.olat.basesecurity.GroupRoles; @@ -63,7 +61,7 @@ import org.olat.core.util.mail.ContactList; import org.olat.core.util.mail.ContactMessage; import org.olat.modules.co.ContactFormController; import org.olat.modules.lecture.LectureBlock; -import org.olat.modules.lecture.LectureBlockAuditLog; +import org.olat.modules.lecture.LectureBlockAppealStatus; import org.olat.modules.lecture.LectureBlockAuditLog.Action; import org.olat.modules.lecture.LectureBlockRollCall; import org.olat.modules.lecture.LectureBlockStatus; @@ -205,14 +203,6 @@ public class ParticipantLectureBlocksController extends FormBasicController { String separator = translate("user.fullname.separator"); List<LectureBlockAndRollCall> rollCalls = lectureService.getParticipantLectureBlocks(entry, assessedIdentity, separator); - List<LectureBlockAuditLog> sendAppealLogs = lectureService.getAuditLog(entry, assessedIdentity, LectureBlockAuditLog.Action.sendAppeal); - Map<Long, Date> appealDates = new HashMap<>(); - for(LectureBlockAuditLog sendAppealLog:sendAppealLogs) { - if(sendAppealLog.getRollCallKey() != null) { - appealDates.put(sendAppealLog.getRollCallKey(), sendAppealLog.getCreationDate()); - } - } - List<LectureBlockAndRollCallRow> rows = new ArrayList<>(rollCalls.size()); for(LectureBlockAndRollCall rollCall:rollCalls) { LectureBlockAndRollCallRow row = new LectureBlockAndRollCallRow(rollCall); @@ -234,17 +224,15 @@ public class ParticipantLectureBlocksController extends FormBasicController { Date endAppeal = CalendarUtils.getEndOfDay(cal).getTime(); Date sendAppealDate = null; + LectureBlockAppealStatus appealStatus = null; if(row.getRow().getRollCallRef() != null ) { - sendAppealDate = appealDates.get(row.getRow().getRollCallRef().getKey()); + sendAppealDate = rollCall.getAppealDate(); + appealStatus = rollCall.getAppealStatus(); } FormLink appealLink = null; if(sendAppealDate != null) { - String appealFrom = translate("appeal.sent", new String[]{ formatter.formatDate(sendAppealDate) }); - appealLink = uifactory.addFormLink("appeal_" + count++, "appealsend", appealFrom, null, flc, Link.LINK | Link.NONTRANSLATED); - appealLink.setTitle(translate("appeal.sent.tooltip", new String[] { formatter.formatDate(sendAppealDate), formatter.formatDate(beginAppeal), formatter.formatDate(endAppeal) })); - appealLink.setEnabled(false); - appealLink.setDomReplacementWrapperRequired(false); + appealLink = getAppealedLink(beginAppeal, endAppeal, sendAppealDate, appealStatus, formatter); } else if(now.compareTo(beginAppeal) >= 0 && now.compareTo(endAppeal) <= 0) { appealLink = uifactory.addFormLink("appeal_" + count++, "appeal", translate("appeal"), null, flc, Link.LINK | Link.NONTRANSLATED); appealLink.setTitle(translate("appeal.tooltip", new String[] { formatter.formatDate(beginAppeal), formatter.formatDate(endAppeal) })); @@ -270,6 +258,28 @@ public class ParticipantLectureBlocksController extends FormBasicController { tableModel.setObjects(rows); tableEl.reset(true, true, true); } + + private FormLink getAppealedLink(Date beginAppeal, Date endAppeal, Date sendAppealDate, + LectureBlockAppealStatus status, Formatter formatter) { + String i18nKey; + if(status == LectureBlockAppealStatus.oldWorkflow) { + i18nKey = "appeal.sent"; + } else if(status == LectureBlockAppealStatus.pending) { + i18nKey = "appeal.pending"; + } else if(status == LectureBlockAppealStatus.rejected) { + i18nKey = "appeal.rejected"; + } else if(status == LectureBlockAppealStatus.approved) { + i18nKey = "appeal.approved"; + } else { + i18nKey = "appeal.sent"; + } + String appealFrom = translate(i18nKey, new String[]{ formatter.formatDate(sendAppealDate) }); + FormLink appealLink = uifactory.addFormLink("appeal_" + count++, "appealsend", appealFrom, null, flc, Link.LINK | Link.NONTRANSLATED); + appealLink.setTitle(translate("appeal.sent.tooltip", new String[] { formatter.formatDate(sendAppealDate), formatter.formatDate(beginAppeal), formatter.formatDate(endAppeal) })); + appealLink.setEnabled(false); + appealLink.setDomReplacementWrapperRequired(false); + return appealLink; + } @Override protected void doDispose() { @@ -378,8 +388,13 @@ public class ParticipantLectureBlocksController extends FormBasicController { LectureBlock lectureBlock = lectureService.getLectureBlock(row.getLectureBlockRef()); LectureBlockRollCall rollCall = lectureService.getRollCall(row.getRollCallRef()); - lectureService.auditLog(Action.sendAppeal, null, null, message, lectureBlock, rollCall, entry, assessedIdentity, null); - + String before = lectureService.toAuditXml(rollCall); + rollCall.setAppealDate(new Date()); + rollCall.setAppealStatus(LectureBlockAppealStatus.pending); + rollCall.setAppealReason(message); + rollCall = lectureService.updateRollCall(rollCall); + String after = lectureService.toAuditXml(rollCall); + lectureService.auditLog(Action.sendAppeal, before, after, message, lectureBlock, rollCall, entry, assessedIdentity, null); dbInstance.commit(); loadModel(); tableEl.reset(false, false, true); diff --git a/src/main/java/org/olat/modules/lecture/ui/_content/appeal_table.html b/src/main/java/org/olat/modules/lecture/ui/_content/appeal_table.html new file mode 100644 index 00000000000..eebadec6df0 --- /dev/null +++ b/src/main/java/org/olat/modules/lecture/ui/_content/appeal_table.html @@ -0,0 +1,2 @@ +$r.render("table") + diff --git a/src/main/java/org/olat/modules/lecture/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/modules/lecture/ui/_i18n/LocalStrings_de.properties index 1cb82e1711a..b6a4cdd7136 100644 --- a/src/main/java/org/olat/modules/lecture/ui/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/modules/lecture/ui/_i18n/LocalStrings_de.properties @@ -11,15 +11,23 @@ all.teachers.switch=Alle Dozenten all.teachers.switch.tooltip.off=Alle Lektionenbl\u00F6cke anzeigen all.teachers.switch.tooltip.on=Nur meine Lektionenbl\u00F6cke anzeigen appeal=Rekurs +appeal.approved=Bestätigt appeal.body=<p>Liebe / Lieber {1}</p><p>Ich melde mich bez\u00FCglich meiner Absenz vom {2}, welche aus meiner Sicht nicht korrekt erfasst wurde.</p><p>Begr\u00FCndung\:</p><p><span style\="color\: \#ff0000;">(Bitte Grund eintragen)</span></p><p>Vielen Dank im Voraus f\u00FCr die Pr\u00FCfung und f\u00FCr eine allf\u00E4llige Korrektur.</p><p>Liebe Gr\u00FCsse</p> appeal.closed=Geschlossen appeal.contact.list=Dozent appeal.from=Ab {0} +appeal.pending=Unerledigt seit {0} +appeal.rejected=Abgelehnt appeal.subject=Rekurs Lektionenblock "{0}" appeal.title=Rekurs f\u00FCr\: "{0}" appeal.tooltip=Rekurs m\u00F6glich von {0} bis {1} appeal.sent=Am {0} geschickt appeal.sent.tooltip=Rekurs wurde am {0} geschickt +appeal.approved=Angenommen +appeal.rejected=Abgelehnt +appeal.pending=Pendent +appeal.status=Status +appeal.form.explain=Entscheid m\u00FCssen begr\u00FCndet werden. archive.entry=Archivierung attendance.list=Absenzenliste attendance.list.title=Absenzenliste\: {0} @@ -199,6 +207,7 @@ remove.custom.rate=Pers\u00F6nlicher Schwellwert entfernen reopen=Wiederge\u00F6ffnet reopen.lecture.blocks=Lektionen wieder\u00F6ffnen repo.lectures=Lektionen +repo.lectures.appeals=Rekurse repo.lectures.block=Lektionenbl\u00F6cke repo.participants=Teilnehmer repo.settings=Konfiguration @@ -229,6 +238,7 @@ sync.teachers.calendar.enabled=Dozentenkalender synchronisieren table.header.absence=Anwesenheit table.header.absent.lectures=Abwesend table.header.actions=<i class\='o_icon o_icon_actions o_icon-lg'> </i> +table.header.appeal.status=Rekurs table.header.attended.current.rate=% Anwesend table.header.attended.lectures=Anwesend table.header.authorized.absence=Entschuldigt diff --git a/src/main/java/org/olat/modules/lecture/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/modules/lecture/ui/_i18n/LocalStrings_en.properties index 96877ff6914..b32a9c87784 100644 --- a/src/main/java/org/olat/modules/lecture/ui/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/modules/lecture/ui/_i18n/LocalStrings_en.properties @@ -11,15 +11,23 @@ all.teachers.switch=All teachers all.teachers.switch.tooltip.off=Show all lecture blocks all.teachers.switch.tooltip.on=Show only my lecture blocks appeal=Appeal +appeal.approved=Approved appeal.body=<p>Dear {1}</p> appeal.closed=Closed appeal.contact.list=Teacher appeal.from=From {0} +appeal.pending=Pending since {0} +appeal.rejected=Rejected appeal.subject=Appeal lecture block "{0}" appeal.title=Appeal for\: "{0}" appeal.tooltip=Appeal possible from {0} until {1} appeal.sent=Send at {0} appeal.sent.tooltip=Appeal was sent at {0} +appeal.approved=Approved +appeal.rejected=Rejected +appeal.pending=Pending +appeal.status=Status +appeal.form.explain=Decision need to be motivated. archive.entry=Archive attendance.list=Absence list attendance.list.title=Absence list: {0} @@ -199,6 +207,7 @@ remove.custom.rate=Remove custom rate reopen=Reopen reopen.lecture.blocks=Reopen lectures repo.lectures=Lectures +repo.lectures.appeals=Appeals repo.lectures.block=Lecture blocks repo.participants=Participants repo.settings=Configuration @@ -229,6 +238,7 @@ sync.teachers.calendar.enabled=Synchronize teachers calendars table.header.absence=Absence table.header.absent.lectures=Absent table.header.actions=<i class\='o_icon o_icon_actions o_icon-lg'> </i> +table.header.appeal.status=Appeal table.header.attended.current.rate=% Attended table.header.attended.lectures=Attended table.header.authorized.absence=Excused diff --git a/src/main/java/org/olat/modules/lecture/ui/component/LectureBlockAppealStatusCellRenderer.java b/src/main/java/org/olat/modules/lecture/ui/component/LectureBlockAppealStatusCellRenderer.java new file mode 100644 index 00000000000..7d5cbbe18d7 --- /dev/null +++ b/src/main/java/org/olat/modules/lecture/ui/component/LectureBlockAppealStatusCellRenderer.java @@ -0,0 +1,51 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.lecture.ui.component; + +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiCellRenderer; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableComponent; +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.modules.lecture.LectureBlockAppealStatus; + +/** + * + * Initial date: 13 juin 2018<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class LectureBlockAppealStatusCellRenderer implements FlexiCellRenderer { + + private final Translator translator; + + public LectureBlockAppealStatusCellRenderer(Translator translator) { + this.translator = translator; + } + + @Override + public void render(Renderer renderer, StringOutput target, Object cellValue, int row, FlexiTableComponent source, URLBuilder ubu, Translator trans) { + if(cellValue instanceof LectureBlockAppealStatus) { + LectureBlockAppealStatus status = (LectureBlockAppealStatus)cellValue; + target.append(translator.translate("appeal.".concat(status.name()))); + } + } +} diff --git a/src/main/java/org/olat/modules/lecture/ui/component/LectureBlockRollCallStatusCellRenderer.java b/src/main/java/org/olat/modules/lecture/ui/component/LectureBlockRollCallStatusCellRenderer.java index b748899751b..379b56938d3 100644 --- a/src/main/java/org/olat/modules/lecture/ui/component/LectureBlockRollCallStatusCellRenderer.java +++ b/src/main/java/org/olat/modules/lecture/ui/component/LectureBlockRollCallStatusCellRenderer.java @@ -28,6 +28,7 @@ import org.olat.core.gui.translator.Translator; import org.olat.modules.lecture.LectureBlockStatus; import org.olat.modules.lecture.LectureRollCallStatus; import org.olat.modules.lecture.model.LectureBlockAndRollCall; +import org.olat.modules.lecture.ui.AppealRollCallRow; import org.olat.modules.lecture.ui.LectureBlockAndRollCallRow; /** @@ -52,11 +53,41 @@ public class LectureBlockRollCallStatusCellRenderer implements FlexiCellRenderer @Override public void render(Renderer renderer, StringOutput target, Object cellValue, int row, FlexiTableComponent source, URLBuilder ubu, Translator trans) { - if(cellValue instanceof LectureBlockAndRollCallRow) { + if(renderer == null) { + if(cellValue instanceof LectureBlockAndRollCallRow) { + LectureBlockAndRollCallRow rollCallRow = (LectureBlockAndRollCallRow)cellValue; + renderString(target, rollCallRow.getRow()); + } else if(cellValue instanceof LectureBlockAndRollCall) { + renderString(target, (LectureBlockAndRollCall)cellValue); + } else if(cellValue instanceof AppealRollCallRow) { + AppealRollCallRow rollCallRow = (AppealRollCallRow)cellValue; + renderString(target, rollCallRow.getLectureBlockAndRollCall()); + } + } else if(cellValue instanceof LectureBlockAndRollCallRow) { LectureBlockAndRollCallRow rollCallRow = (LectureBlockAndRollCallRow)cellValue; render(target, rollCallRow.getRow()); } else if(cellValue instanceof LectureBlockAndRollCall) { render(target, (LectureBlockAndRollCall)cellValue); + } else if(cellValue instanceof AppealRollCallRow) { + AppealRollCallRow rollCallRow = (AppealRollCallRow)cellValue; + render(target, rollCallRow.getLectureBlockAndRollCall()); + } + } + + private void renderString(StringOutput target, LectureBlockAndRollCall rollCall) { + if(rollCall.isRollCalled()) { + LectureBlockStatus status = rollCall.getStatus(); + LectureRollCallStatus rollCallStatus = rollCall.getRollCallStatus(); + if(status == LectureBlockStatus.cancelled) { + target.append(translator.translate("cancelled")); + } else if(status == LectureBlockStatus.done + && (rollCallStatus == LectureRollCallStatus.closed || rollCallStatus == LectureRollCallStatus.autoclosed)) { + renderClosed(target, rollCall, true); + } else { + target.append(translator.translate("in.progress")); + } + } else if(!rollCall.isCompulsory()) { + target.append(translator.translate("rollcall.tooltip.free")); } } @@ -69,7 +100,7 @@ public class LectureBlockRollCallStatusCellRenderer implements FlexiCellRenderer target.append("<span title='").append(title).append("'><i class='o_icon o_icon-lg o_icon_cancelled'> </i></span>"); } else if(status == LectureBlockStatus.done && (rollCallStatus == LectureRollCallStatus.closed || rollCallStatus == LectureRollCallStatus.autoclosed)) { - renderClosed(target, rollCall); + renderClosed(target, rollCall, false); } else { String title = translator.translate("in.progress"); target.append("<span title='").append(title).append("'><i class='o_icon o_icon-lg o_icon_status_in_review'> </i></span>"); @@ -80,7 +111,7 @@ public class LectureBlockRollCallStatusCellRenderer implements FlexiCellRenderer } } - private void renderClosed(StringOutput target, LectureBlockAndRollCall rollCall) { + private void renderClosed(StringOutput target, LectureBlockAndRollCall rollCall, boolean textOnly) { int numOfLectures = rollCall.getEffectiveLecturesNumber(); if(numOfLectures < 0) { numOfLectures = rollCall.getPlannedLecturesNumber(); @@ -111,6 +142,10 @@ public class LectureBlockRollCallStatusCellRenderer implements FlexiCellRenderer iconCssClass = "o_lectures_rollcall_free"; title = translator.translate("rollcall.tooltip.free"); } - target.append("<span title='").append(title).append("'><i class='o_icon o_icon-lg ").append(iconCssClass).append("'> </i></span>"); + if(textOnly) { + target.append(title); + } else { + target.append("<span title='").append(title).append("'><i class='o_icon o_icon-lg ").append(iconCssClass).append("'> </i></span>"); + } } } diff --git a/src/main/java/org/olat/modules/lecture/ui/component/LecturesCompulsoryRenderer.java b/src/main/java/org/olat/modules/lecture/ui/component/LecturesCompulsoryRenderer.java index b3ce392cc02..c3dcbd27321 100644 --- a/src/main/java/org/olat/modules/lecture/ui/component/LecturesCompulsoryRenderer.java +++ b/src/main/java/org/olat/modules/lecture/ui/component/LecturesCompulsoryRenderer.java @@ -25,6 +25,8 @@ 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.modules.lecture.model.LectureBlockAndRollCall; +import org.olat.modules.lecture.ui.AppealRollCallRow; import org.olat.modules.lecture.ui.LectureBlockAndRollCallRow; /** @@ -42,16 +44,23 @@ public class LecturesCompulsoryRenderer implements FlexiCellRenderer { Object obj = source.getFlexiTableElement().getTableDataModel().getObject(row); if(obj instanceof LectureBlockAndRollCallRow) { LectureBlockAndRollCallRow rollCallRow = (LectureBlockAndRollCallRow)obj; - if(!rollCallRow.getRow().isCompulsory()) { - target.append("<span class='o_lecture_free'>") - .append(cellValue.toString()) - .append(" *</span>"); - } else { - target.append(cellValue.toString()); - } + render(target, cellValue, rollCallRow.getRow()); + } else if(obj instanceof AppealRollCallRow) { + AppealRollCallRow rollCallRow = (AppealRollCallRow)obj; + render(target, cellValue, rollCallRow.getLectureBlockAndRollCall()); } else { target.append(cellValue.toString()); } } } + + private void render(StringOutput target, Object cellValue, LectureBlockAndRollCall rollCallRow) { + if(!rollCallRow.isCompulsory()) { + target.append("<span class='o_lecture_free'>") + .append(cellValue.toString()) + .append(" *</span>"); + } else { + target.append(cellValue.toString()); + } + } } diff --git a/src/main/java/org/olat/modules/lecture/ui/export/LectureBlockAuditLogExport.java b/src/main/java/org/olat/modules/lecture/ui/export/LectureBlockAuditLogExport.java index a9326e37fda..d92eae853fe 100644 --- a/src/main/java/org/olat/modules/lecture/ui/export/LectureBlockAuditLogExport.java +++ b/src/main/java/org/olat/modules/lecture/ui/export/LectureBlockAuditLogExport.java @@ -70,12 +70,12 @@ public class LectureBlockAuditLogExport extends AbstractLectureBlockAuditLogExpo int pos = 0; headerRow.addCell(pos++, translator.translate("export.header.entry", new String[] { entry.getDisplayname() })); - Formatter formatter = Formatter.getInstance(translator.getLocale()); + Formatter localeFormatter = Formatter.getInstance(translator.getLocale()); String[] args = new String[] { lectureBlock.getTitle(), - formatter.formatDate(lectureBlock.getStartDate()), - formatter.formatTimeShort(lectureBlock.getStartDate()), - formatter.formatTimeShort(lectureBlock.getEndDate()) + localeFormatter.formatDate(lectureBlock.getStartDate()), + localeFormatter.formatTimeShort(lectureBlock.getStartDate()), + localeFormatter.formatTimeShort(lectureBlock.getEndDate()) }; headerRow.addCell(pos++, translator.translate("export.header.lectureblocks", args)); } diff --git a/src/main/java/org/olat/upgrade/OLATUpgrade_13_0_0.java b/src/main/java/org/olat/upgrade/OLATUpgrade_13_0_0.java index b750513c47d..2d85c0749fa 100644 --- a/src/main/java/org/olat/upgrade/OLATUpgrade_13_0_0.java +++ b/src/main/java/org/olat/upgrade/OLATUpgrade_13_0_0.java @@ -30,10 +30,6 @@ import org.olat.basesecurity.manager.OrganisationDAO; import org.olat.core.commons.persistence.DB; import org.olat.core.id.Identity; import org.olat.core.id.Organisation; -import org.olat.repository.RepositoryEntry; -import org.olat.repository.RepositoryService; -import org.olat.repository.manager.RepositoryEntryRelationDAO; -import org.olat.repository.manager.RepositoryEntryToOrganisationDAO; import org.olat.modules.forms.EvaluationFormManager; import org.olat.modules.forms.EvaluationFormParticipation; import org.olat.modules.forms.EvaluationFormParticipationStatus; @@ -42,7 +38,16 @@ import org.olat.modules.forms.EvaluationFormSessionStatus; import org.olat.modules.forms.EvaluationFormSurvey; import org.olat.modules.forms.model.jpa.EvaluationFormParticipationImpl; import org.olat.modules.forms.model.jpa.EvaluationFormSessionImpl; +import org.olat.modules.lecture.LectureBlockAppealStatus; +import org.olat.modules.lecture.LectureBlockAuditLog; +import org.olat.modules.lecture.LectureBlockRollCall; +import org.olat.modules.lecture.LectureService; +import org.olat.modules.lecture.model.LectureBlockRollCallRefImpl; import org.olat.modules.portfolio.PortfolioService; +import org.olat.repository.RepositoryEntry; +import org.olat.repository.RepositoryService; +import org.olat.repository.manager.RepositoryEntryRelationDAO; +import org.olat.repository.manager.RepositoryEntryToOrganisationDAO; import org.springframework.beans.factory.annotation.Autowired; /** @@ -57,10 +62,13 @@ public class OLATUpgrade_13_0_0 extends OLATUpgrade { private static final String MIGRATE_ROLE = "MIGRATE ROLE"; private static final String MIGRATE_REPO_ENTRY_DEFAULT_ORG = "MIGRATE REPO ENTRY TO DEF ORG"; private static final String MIGRATE_PORTFOLIO_EVAL_FORM = "PORTFOLIO EVALUATION FORM"; + private static final String MIGRATE_SEND_APPEAL_DATES = "LECTURES SEND APPEAL DATES"; @Autowired private DB dbInstance; @Autowired + private LectureService lectureService; + @Autowired private OrganisationDAO organisationDao; @Autowired private OrganisationService organisationService; @@ -103,6 +111,7 @@ public class OLATUpgrade_13_0_0 extends OLATUpgrade { allOk &= migrateRole(upgradeManager, uhd); allOk &= migrateRepositoryEntriesTodefaultOrganisation(upgradeManager, uhd); allOk &= migratePortfolioEvaluationForm(upgradeManager, uhd); + allOk &= migrateLecturesSendAppealDates(upgradeManager, uhd); uhd.setInstallationComplete(allOk); upgradeManager.setUpgradesHistory(uhd, VERSION); @@ -114,6 +123,71 @@ public class OLATUpgrade_13_0_0 extends OLATUpgrade { return allOk; } + private boolean migrateLecturesSendAppealDates(UpgradeManager upgradeManager, UpgradeHistoryData uhd) { + boolean allOk = true; + if (!uhd.getBooleanDataValue(MIGRATE_SEND_APPEAL_DATES)) { + try { + List<Long> repositoryEntryKeys = getRepositoryEntryWithAuditLog(); + for(int i=0; i<repositoryEntryKeys.size(); i++) { + migrateLecturesSendAppealDates(repositoryEntryKeys.get(0)); + if(i % 50 == 0) { + log.info("Migration repository entries with appeal in lectures block roll call: " + i + " / " + repositoryEntryKeys.size()); + } + } + log.info("Migration repository entries with appeal in lectures block roll call: " + repositoryEntryKeys.size()); + dbInstance.commitAndCloseSession(); + } catch (Exception e) { + log.error("", e); + allOk &= false; + } + + uhd.setBooleanDataValue(MIGRATE_SEND_APPEAL_DATES, allOk); + upgradeManager.setUpgradesHistory(uhd, VERSION); + } + return allOk; + } + + private void migrateLecturesSendAppealDates(Long repositoryEntryKey) { + int count = 0; + + List<LectureBlockAuditLog> auditLogs = getAuditLog(repositoryEntryKey); + for(LectureBlockAuditLog auditLog:auditLogs) { + Long rollCallKey = auditLog.getRollCallKey(); + if(rollCallKey != null) { + LectureBlockRollCall call = lectureService.getRollCall(new LectureBlockRollCallRefImpl(rollCallKey)); + if(call.getAppealDate() == null) { + call.setAppealDate(auditLog.getCreationDate()); + call.setAppealStatus(LectureBlockAppealStatus.oldWorkflow); + lectureService.updateRollCall(call); + if(count++ % 25 == 0) { + dbInstance.commitAndCloseSession(); + } + } + } + } + + dbInstance.commitAndCloseSession(); + } + + private List<LectureBlockAuditLog> getAuditLog(Long entryKey) { + StringBuilder sb = new StringBuilder(256); + sb.append("select log from lectureblockauditlog log where log.entryKey=:repoEntryKey and log.action=:action"); + return dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), LectureBlockAuditLog.class) + .setParameter("repoEntryKey", entryKey) + .setParameter("action", LectureBlockAuditLog.Action.sendAppeal.name()) + .getResultList(); + } + + private List<Long> getRepositoryEntryWithAuditLog() { + StringBuilder sb = new StringBuilder(256); + sb.append("select distinct log.entryKey from lectureblockauditlog log where log.action=:action"); + return dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), Long.class) + .setParameter("action", LectureBlockAuditLog.Action.sendAppeal.name()) + .getResultList(); + } + private boolean migrateRepositoryEntriesTodefaultOrganisation(UpgradeManager upgradeManager, UpgradeHistoryData uhd) { boolean allOk = true; if (!uhd.getBooleanDataValue(MIGRATE_REPO_ENTRY_DEFAULT_ORG)) { diff --git a/src/main/resources/database/mysql/alter_12_4_x_to_13_0_0.sql b/src/main/resources/database/mysql/alter_12_4_x_to_13_0_0.sql index 5ab1f096d05..eda7b3303bc 100644 --- a/src/main/resources/database/mysql/alter_12_4_x_to_13_0_0.sql +++ b/src/main/resources/database/mysql/alter_12_4_x_to_13_0_0.sql @@ -203,3 +203,8 @@ create index idx_eva_resp_report_idx on o_eva_form_response (fk_session, e_respo alter table o_bs_group_member add column g_inheritance_mode varchar(16) default 'none' not null; +-- lectures +alter table o_lecture_block_roll_call add column l_appeal_reason mediumtext; +alter table o_lecture_block_roll_call add column l_appeal_status mediumtext; +alter table o_lecture_block_roll_call add column l_appeal_status_reason mediumtext; + diff --git a/src/main/resources/database/mysql/setupDatabase.sql b/src/main/resources/database/mysql/setupDatabase.sql index 2cf866fa487..a3d560f625c 100644 --- a/src/main/resources/database/mysql/setupDatabase.sql +++ b/src/main/resources/database/mysql/setupDatabase.sql @@ -2123,6 +2123,9 @@ create table o_lecture_block_roll_call ( l_absence_authorized bit default null, l_absence_appeal_date datetime, l_absence_supervisor_noti_date datetime, + l_appeal_reason mediumtext, + l_appeal_status mediumtext, + l_appeal_status_reason mediumtext, fk_lecture_block bigint not null, fk_identity bigint not null, primary key (id) diff --git a/src/main/resources/database/oracle/alter_12_4_x_to_13_0_0.sql b/src/main/resources/database/oracle/alter_12_4_x_to_13_0_0.sql index 7b70ecdc8c9..320ff70f066 100644 --- a/src/main/resources/database/oracle/alter_12_4_x_to_13_0_0.sql +++ b/src/main/resources/database/oracle/alter_12_4_x_to_13_0_0.sql @@ -202,3 +202,11 @@ create index idx_eva_resp_report_idx on o_eva_form_response (fk_session, e_respo -- membership alter table o_bs_group_member add g_inheritance_mode varchar(16) default 'none' not null; + +-- lectures +alter table o_lecture_block_roll_call add l_appeal_reason CLOB; +alter table o_lecture_block_roll_call add l_appeal_status CLOB; +alter table o_lecture_block_roll_call add l_appeal_status_reason CLOB; + + + diff --git a/src/main/resources/database/oracle/setupDatabase.sql b/src/main/resources/database/oracle/setupDatabase.sql index 3ac9cf89341..77d4fe1db6d 100644 --- a/src/main/resources/database/oracle/setupDatabase.sql +++ b/src/main/resources/database/oracle/setupDatabase.sql @@ -2199,6 +2199,9 @@ create table o_lecture_block_roll_call ( l_absence_authorized number default null, l_absence_appeal_date date, l_absence_supervisor_noti_date date, + l_appeal_reason CLOB, + l_appeal_status CLOB, + l_appeal_status_reason CLOB, fk_lecture_block number(20) not null, fk_identity number(20) not null, primary key (id) diff --git a/src/main/resources/database/postgresql/alter_12_4_x_to_13_0_0.sql b/src/main/resources/database/postgresql/alter_12_4_x_to_13_0_0.sql index 379a80fbf81..798a8871074 100644 --- a/src/main/resources/database/postgresql/alter_12_4_x_to_13_0_0.sql +++ b/src/main/resources/database/postgresql/alter_12_4_x_to_13_0_0.sql @@ -158,7 +158,7 @@ create table o_eva_form_survey ( creationdate timestamp not null, lastmodified timestamp not null, e_resname varchar(50) not null, - e_resid bigint not null, + e_resid int8 not null, e_sub_ident varchar(2048), fk_form_entry bigint not null, primary key (id) @@ -208,3 +208,13 @@ create index idx_eva_resp_report_idx on o_eva_form_response (fk_session, e_respo -- membership alter table o_bs_group_member add column g_inheritance_mode varchar(16) default 'none' not null; + +-- lectures +alter table o_lecture_block_roll_call add column l_appeal_reason text; +alter table o_lecture_block_roll_call add column l_appeal_status text; +alter table o_lecture_block_roll_call add column l_appeal_status_reason text; + + + + + diff --git a/src/main/resources/database/postgresql/setupDatabase.sql b/src/main/resources/database/postgresql/setupDatabase.sql index ae96aa22121..f700bf1d4c5 100644 --- a/src/main/resources/database/postgresql/setupDatabase.sql +++ b/src/main/resources/database/postgresql/setupDatabase.sql @@ -2149,6 +2149,9 @@ create table o_lecture_block_roll_call ( l_absence_authorized bool default null, l_absence_appeal_date timestamp, l_absence_supervisor_noti_date timestamp, + l_appeal_reason text, + l_appeal_status text, + l_appeal_status_reason text, fk_lecture_block int8 not null, fk_identity int8 not null, primary key (id) -- GitLab