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