From d5d967cae70aae87166dfa0ef03d64e78c86e11e Mon Sep 17 00:00:00 2001 From: srosse <stephane.rosse@frentix.com> Date: Mon, 2 Sep 2019 15:09:14 +0200 Subject: [PATCH] OO-4150: implement batch authorization of absence notices --- .../olat/modules/lecture/LectureModule.java | 4 +- .../olat/modules/lecture/LectureService.java | 3 + .../lecture/manager/LectureServiceImpl.java | 52 +++++++++- .../ui/_i18n/LocalStrings_de.properties | 4 +- .../ui/_i18n/LocalStrings_en.properties | 6 +- .../coach/AbsenceNoticesListController.java | 54 ++++++++--- ...nfirmAuthorizeAbsenceNoticeController.java | 97 +++++++++++++++++++ .../ui/coach/_content/authorize_notices.html | 5 + 8 files changed, 204 insertions(+), 21 deletions(-) create mode 100644 src/main/java/org/olat/modules/lecture/ui/coach/ConfirmAuthorizeAbsenceNoticeController.java create mode 100644 src/main/java/org/olat/modules/lecture/ui/coach/_content/authorize_notices.html diff --git a/src/main/java/org/olat/modules/lecture/LectureModule.java b/src/main/java/org/olat/modules/lecture/LectureModule.java index 2be255f7a2d..2fd9e63bcd5 100644 --- a/src/main/java/org/olat/modules/lecture/LectureModule.java +++ b/src/main/java/org/olat/modules/lecture/LectureModule.java @@ -179,13 +179,15 @@ public class LectureModule extends AbstractSpringModule implements ConfigOnOff { // Add controller factory extension point to launch groups NewControllerFactory.getInstance().addContextEntryControllerCreator("Lectures", new LecturesManagementContextEntryControllerCreator()); + NewControllerFactory.getInstance().addContextEntryControllerCreator("LecturesManagementSite", + new LecturesManagementContextEntryControllerCreator()); updateProperties(); } @Override protected void initFromChangedProperties() { - init(); + updateProperties(); } private void updateProperties() { diff --git a/src/main/java/org/olat/modules/lecture/LectureService.java b/src/main/java/org/olat/modules/lecture/LectureService.java index c8f0e93e394..214eeea05d3 100644 --- a/src/main/java/org/olat/modules/lecture/LectureService.java +++ b/src/main/java/org/olat/modules/lecture/LectureService.java @@ -318,6 +318,9 @@ public interface LectureService { public AbsenceNotice updateAbsenceNotice(AbsenceNotice absenceNotice, Identity authorizer, List<RepositoryEntry> entries, List<LectureBlock> lectureBlocks, Identity actingIdentity); + public AbsenceNotice updateAbsenceNoticeAuthorization(AbsenceNotice absenceNotice, Identity authorizer, + Boolean authorize, Identity actingIdentity); + /** * Delete the absence notice and remove the absences from the roll calls. * 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 111c6c32f64..d78539057d3 100644 --- a/src/main/java/org/olat/modules/lecture/manager/LectureServiceImpl.java +++ b/src/main/java/org/olat/modules/lecture/manager/LectureServiceImpl.java @@ -586,7 +586,6 @@ public class LectureServiceImpl implements LectureService, UserDataDeletable, De Identity assessedIdentity = notice.getIdentity(); AbsenceNoticeRelationsAuditImpl beforeRelations = new AbsenceNoticeRelationsAuditImpl(); - List<AbsenceNoticeToLectureBlock> noticeToBlocks = absenceNoticeToLectureBlockDao.getRelations(absenceNotice); if(noticeToBlocks != null && !noticeToBlocks.isEmpty()) { beforeRelations.setNoticeToBlocks(noticeToBlocks); @@ -737,13 +736,28 @@ public class LectureServiceImpl implements LectureService, UserDataDeletable, De if(currentRollCallSet.contains(rollCall)) { currentRollCallSet.remove(rollCall); } else { + List<Integer> absences = new ArrayList<>(); + LectureBlock lectureBlock = rollCall.getLectureBlock(); + for(int i=0; i<lectureBlock.getPlannedLecturesNumber(); i++) { + absences.add(Integer.valueOf(i)); + } rollCall.setAbsenceNotice(notice); + rollCall.setAbsenceAuthorized(notice.getAbsenceAuthorized()); + lectureBlockRollCallDao.addLecture(lectureBlock, rollCall, absences); lectureBlockRollCallDao.update(rollCall); } } for(LectureBlockRollCall toUnlink: currentRollCallSet) { toUnlink.setAbsenceNotice(null); + + LectureBlock lectureBlock = toUnlink.getLectureBlock(); + List<Integer> absenceToRemove = new ArrayList<>(); + for(int i=0; i<lectureBlock.getPlannedLecturesNumber(); i++) { + absenceToRemove.add(Integer.valueOf(i)); + } + lectureBlockRollCallDao.removeLecture(lectureBlock, toUnlink, absenceToRemove); + toUnlink.setAbsenceAuthorized(null); lectureBlockRollCallDao.update(toUnlink); } @@ -751,7 +765,41 @@ public class LectureServiceImpl implements LectureService, UserDataDeletable, De after.setRollCalls(rollCalls); } } - + + @Override + public AbsenceNotice updateAbsenceNoticeAuthorization(AbsenceNotice absenceNotice, Identity authorizer, + Boolean authorize, Identity actingIdentity) { + absenceNotice = absenceNoticeDao.loadAbsenceNotice(absenceNotice.getKey()); + if(absenceNotice != null) { + String beforeNotice = toAuditXml(absenceNotice); + + absenceNotice.setAbsenceAuthorized(authorize); + ((AbsenceNoticeImpl)absenceNotice).setAuthorizer(authorizer); + absenceNotice = absenceNoticeDao.updateAbsenceNotice(absenceNotice); + dbInstance.commit(); + + List<LectureBlockRollCall> rollCalls = absenceNoticeDao.getRollCalls(absenceNotice); + for(LectureBlockRollCall rollCall:rollCalls) { + if((authorize == null && rollCall.getAbsenceAuthorized() != null) + || (authorize != null && !authorize.equals(rollCall.getAbsenceAuthorized()))) { + String before = toAuditXml(rollCall); + rollCall.setAbsenceAuthorized(authorize); + rollCall = lectureBlockRollCallDao.update(rollCall); + String after = toAuditXml(rollCall); + + LectureBlock lectureBlock = rollCall.getLectureBlock(); + auditLogDao.auditLog(Action.updateRollCall, before, after, null, + lectureBlock, rollCall, lectureBlock.getEntry(), rollCall.getIdentity(), actingIdentity); + } + } + dbInstance.commit(); + + String afterNotice = toAuditXml(absenceNotice); + auditLog(Action.updateAbsenceNotice, beforeNotice, afterNotice, null, absenceNotice, absenceNotice.getIdentity(), actingIdentity); + } + return absenceNotice; + } + @Override public AbsenceNotice updateAbsenceNoticeAttachments(AbsenceNotice absenceNotice, List<VFSItem> newFiles, List<VFSItem> filesToDelete) { if(!filesToDelete.isEmpty()) { 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 a9af30502c9..c9868755fe0 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 @@ -91,10 +91,11 @@ config.rollcall.enabled=Anwesenheitskontrolle einschalten config.sync.course.calendar=Kurs Kalender synchronisieren config.sync.participant.calendar=Teilnehmer Kalender synchronisieren config.sync.teacher.calendar=Dozentenkalender synchronisieren +confirm.authorize=Wollen Sie die Absenz/Dispenz f\u00FCr "<strong>{0}</strong>" entschudligen? confirm.delete.absence.category=Wollen Sie wirklich die Absenzenbegr\u00FCndung "{0}" l\u00F6schen? confirm.delete.absence=Wollen Sie die Absenze von "{0}" l\u00F6schen? Die Person wird als anwesend betrachtet. confirm.delete.absence.notice=Wollen Sie die Abwesenheit von "{0}" l\u00F6schen? Die Person wird als anwesend betrachtet. -confirm.delete.dispensation=Wollen Sie die Dispenz von "{0}" l\u00F6schen? Die Person wird als anwesend betrachtet. +confirm.delete.dispensation=Wollen Sie die Dispens von "{0}" l\u00F6schen? Die Person wird als anwesend betrachtet. confirm.delete.assessment.mode.text=Wollen Sie wirklich diesen Pr\u00FCfungsmodus von Lektionenblock "{0}" l\u00F6schen? confirm.delete.assessment.mode.title=Pr\u00FCfungsmodus l\u00F6schen confirm.delete.lectures=Wollen Sie wirklich diesen Lektionenblock "{0}" l\u00F6schen? @@ -503,6 +504,7 @@ user.overview.dispensation=Dispensen user.overview.lectures=Lektionen und Absenzen user.profil=Benutzerprofil warning.choose.at.least.one.appeal=Sie m\u00FCssen mindestens einen Rekurs w\u00E4hlen. +warning.choose.at.least.one.notice=Sie m\u00FCssen mindestens eine Absenz/Dispens w\u00E4hlen. warning.edit.lecture=Absenzenerfassung ist deaktiviert. warning.repositoryentry.deleted=$org.olat.repository\:repositoryentry.deleted warning.teachers.at.least.one.contact=Es gibt kein Dozent zu kontaktieren 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 5a9c047f13b..d11b09611ac 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 @@ -91,6 +91,7 @@ config.rollcall.enabled=Roll call enabled config.sync.course.calendar=Synchronize course calendar config.sync.participant.calendar=Synchronize participants calendar config.sync.teacher.calendar=Synchronize teacher calendar +confirm.authorize=Do you want to authorize the absence/dispensation for "<strong>{0}</strong>"? confirm.delete.absence=Do you really want to delete the absence of "{0}"? The person will be considered present. confirm.delete.absence.category=Do you really want to delete this reason of absence "{0}"? confirm.delete.absence.notice=Do you really want to delete the notice of absence of "{0}"? The person will be considered present. @@ -500,9 +501,10 @@ upto=till {0} user.overview.appeals=Appeals user.overview.dispensation=Dispensation user.overview.lectures=Lectures and absences -user.profil=User profil +user.profil=User profile warning.choose.at.least.one.appeal=You need to choose at least one appeal -warning.edit.lecture=Roll call ist deactivated. +warning.choose.at.least.one.notice=You need to choose at least one absence/dispensation +warning.edit.lecture=Roll call is deactivated. warning.repositoryentry.deleted=$org.olat.repository\:repositoryentry.deleted warning.teachers.at.least.one.contact=There isn't any teacher to contact whole.day=Whole day diff --git a/src/main/java/org/olat/modules/lecture/ui/coach/AbsenceNoticesListController.java b/src/main/java/org/olat/modules/lecture/ui/coach/AbsenceNoticesListController.java index e6475eb4832..c2372700e84 100644 --- a/src/main/java/org/olat/modules/lecture/ui/coach/AbsenceNoticesListController.java +++ b/src/main/java/org/olat/modules/lecture/ui/coach/AbsenceNoticesListController.java @@ -124,6 +124,7 @@ public class AbsenceNoticesListController extends FormBasicController { private AbsenceNoticeDetailsCalloutController detailsCtrl; private RepositoryEntriesCalloutController entriesCalloutCtrl; private ConfirmDeleteAbsenceNoticeController deleteNoticeCtrl; + private ConfirmAuthorizeAbsenceNoticeController authorizeCtrl; @Autowired private UserManager userManager; @@ -362,7 +363,7 @@ public class AbsenceNoticesListController extends FormBasicController { toolsCalloutCtrl.deactivate(); cleanUp(); } - } else if(this.deleteNoticeCtrl == source) { + } else if(deleteNoticeCtrl == source || authorizeCtrl == source) { if(event == Event.DONE_EVENT) { loadModel(lastSearchParams); } @@ -371,9 +372,7 @@ public class AbsenceNoticesListController extends FormBasicController { } else if(contactTeachersCtrl == source) { cmc.deactivate(); cleanUp(); - } else if(toolsCalloutCtrl == source) { - cleanUp(); - } else if(cmc == source) { + } else if(toolsCalloutCtrl == source || cmc == source) { cleanUp(); } super.event(ureq, source, event); @@ -385,6 +384,7 @@ public class AbsenceNoticesListController extends FormBasicController { removeAsListenerAndDispose(toolsCalloutCtrl); removeAsListenerAndDispose(deleteNoticeCtrl); removeAsListenerAndDispose(editNoticeCtrl); + removeAsListenerAndDispose(authorizeCtrl); removeAsListenerAndDispose(detailsCtrl); removeAsListenerAndDispose(toolsCtrl); removeAsListenerAndDispose(cmc); @@ -393,6 +393,7 @@ public class AbsenceNoticesListController extends FormBasicController { toolsCalloutCtrl = null; deleteNoticeCtrl = null; editNoticeCtrl = null; + authorizeCtrl = null; detailsCtrl = null; toolsCtrl = null; cmc = null; @@ -400,7 +401,18 @@ public class AbsenceNoticesListController extends FormBasicController { @Override protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { - if(source instanceof FormLink) { + if(authorizeButton == source) { + doAuthorize(ureq); + } else if(unauthorizedFilterEl == source) { + doFilterUnauthorized(); + } else if(tableEl == source) { + if(event instanceof SelectionEvent) { + SelectionEvent se = (SelectionEvent)event; + if("select-user".equals(se.getCommand())) { + doSelectUser(ureq, tableModel.getObject(se.getIndex())); + } + } + } else if(source instanceof FormLink) { FormLink link = (FormLink)source; if("details".equals(link.getCmd())) { doOpenDetails(ureq, (AbsenceNoticeRow)link.getUserObject(), link); @@ -411,16 +423,7 @@ public class AbsenceNoticesListController extends FormBasicController { } else if("entries".equals(link.getCmd())) { doOpenEntries(ureq, (AbsenceNoticeRow)link.getUserObject(), link); } - } else if(unauthorizedFilterEl == source) { - doFilterUnauthorized(); - } else if(tableEl == source) { - if(event instanceof SelectionEvent) { - SelectionEvent se = (SelectionEvent)event; - if("select-user".equals(se.getCommand())) { - doSelectUser(ureq, tableModel.getObject(se.getIndex())); - } - } - } + } super.formInnerEvent(ureq, source, event); } @@ -441,6 +444,26 @@ public class AbsenceNoticesListController extends FormBasicController { loadModel(lastSearchParams); } + private void doAuthorize(UserRequest ureq) { + Set<Integer> selectedIndex = tableEl.getMultiSelectedIndex(); + List<AbsenceNotice> notices = selectedIndex.stream() + .map(index -> tableModel.getObject(index.intValue())) + .map(AbsenceNoticeRow::getAbsenceNotice) + .collect(Collectors.toList()); + + if(notices.isEmpty()) { + showWarning("warning.choose.at.least.one.notice"); + } else { + authorizeCtrl = new ConfirmAuthorizeAbsenceNoticeController(ureq, getWindowControl(), notices); + listenTo(authorizeCtrl); + + String title = translate("absences.batch.authorize"); + cmc = new CloseableModalController(getWindowControl(), "close", authorizeCtrl.getInitialComponent(), true, title, true); + listenTo(cmc); + cmc.activate(); + } + } + protected void doSearch(AbsenceNoticeSearchParameters searchParams) { loadModel(searchParams); } @@ -532,6 +555,7 @@ public class AbsenceNoticesListController extends FormBasicController { toolsCalloutCtrl.activate(); } } + private void doOpenEntry(UserRequest ureq, RepositoryEntry entry) { String businessPath = "[RepositoryEntry:" + entry.getKey() + "]"; NewControllerFactory.getInstance().launch(businessPath, ureq, getWindowControl()); diff --git a/src/main/java/org/olat/modules/lecture/ui/coach/ConfirmAuthorizeAbsenceNoticeController.java b/src/main/java/org/olat/modules/lecture/ui/coach/ConfirmAuthorizeAbsenceNoticeController.java new file mode 100644 index 00000000000..cc941259024 --- /dev/null +++ b/src/main/java/org/olat/modules/lecture/ui/coach/ConfirmAuthorizeAbsenceNoticeController.java @@ -0,0 +1,97 @@ +/** + * <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.coach; + +import java.util.ArrayList; +import java.util.List; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +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.util.Util; +import org.olat.modules.lecture.AbsenceNotice; +import org.olat.modules.lecture.LectureService; +import org.olat.modules.lecture.ui.LectureRepositoryAdminController; +import org.olat.user.UserManager; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 30 août 2019<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class ConfirmAuthorizeAbsenceNoticeController extends FormBasicController { + + private final List<AbsenceNotice> notices; + + @Autowired + private UserManager userManager; + @Autowired + private LectureService lectureService; + + public ConfirmAuthorizeAbsenceNoticeController(UserRequest ureq, WindowControl wControl, List<AbsenceNotice> notices) { + super(ureq, wControl, "authorize_notices", Util.createPackageTranslator(LectureRepositoryAdminController.class, ureq.getLocale())); + this.notices = new ArrayList<>(notices); + initForm(ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + if(formLayout instanceof FormLayoutContainer) { + FormLayoutContainer layoutCont = (FormLayoutContainer)formLayout; + StringBuilder sb = new StringBuilder(); + for(AbsenceNotice notice:notices) { + String fullName = userManager.getUserDisplayName(notice.getIdentity()); + if(sb.indexOf(fullName) < 0) { + if(sb.length() > 0) sb.append(","); + sb.append(fullName); + } + } + String message = translate("confirm.authorize", new String[] { sb.toString() }); + layoutCont.contextPut("message", message); + } + + uifactory.addFormCancelButton("cancel", formLayout, ureq, getWindowControl()); + uifactory.addFormSubmitButton("absences.batch.authorize", formLayout); + } + + @Override + protected void doDispose() { + // + } + + @Override + protected void formCancelled(UserRequest ureq) { + fireEvent(ureq, Event.CANCELLED_EVENT); + } + + @Override + protected void formOK(UserRequest ureq) { + for(AbsenceNotice notice:notices) { + lectureService.updateAbsenceNoticeAuthorization(notice, getIdentity(), Boolean.TRUE, getIdentity()); + } + fireEvent(ureq, Event.DONE_EVENT); + } +} diff --git a/src/main/java/org/olat/modules/lecture/ui/coach/_content/authorize_notices.html b/src/main/java/org/olat/modules/lecture/ui/coach/_content/authorize_notices.html new file mode 100644 index 00000000000..7eeab27569a --- /dev/null +++ b/src/main/java/org/olat/modules/lecture/ui/coach/_content/authorize_notices.html @@ -0,0 +1,5 @@ +<p>$message</p> +<div class="o_button_group"> + $r.render("cancel") + $r.render("absences.batch.authorize") +</div> \ No newline at end of file -- GitLab