From e1920bd7d92f2437cebb8b5932b93472f8932d63 Mon Sep 17 00:00:00 2001
From: srosse <stephane.rosse@frentix.com>
Date: Thu, 5 Dec 2019 18:40:08 +0100
Subject: [PATCH] OO-4387: delete absence notice with a unique relation to
 deleted blocks

---
 .../olat/modules/lecture/LectureService.java  |   4 +-
 .../AbsenceNoticeToLectureBlockDAO.java       |  39 ++++
 .../lecture/manager/LectureServiceImpl.java   |  40 +++-
 .../restapi/LectureBlockWebService.java       |   4 +-
 .../ConfirmDeleteLectureBlockController.java  | 171 ++++++++++++++
 .../ui/LectureListRepositoryController.java   |  77 ++-----
 .../lecture/ui/_content/confirm_delete.html   |  15 ++
 .../ui/_i18n/LocalStrings_de.properties       |   2 +
 .../ui/_i18n/LocalStrings_en.properties       |   2 +
 .../AbsenceNoticeToLectureBlockDAOTest.java   |  30 +++
 .../lecture/manager/LectureServiceTest.java   | 212 ++++++++++++++++++
 11 files changed, 537 insertions(+), 59 deletions(-)
 create mode 100644 src/main/java/org/olat/modules/lecture/ui/ConfirmDeleteLectureBlockController.java
 create mode 100644 src/main/java/org/olat/modules/lecture/ui/_content/confirm_delete.html

diff --git a/src/main/java/org/olat/modules/lecture/LectureService.java b/src/main/java/org/olat/modules/lecture/LectureService.java
index 249e79a6102..19e6ba480e6 100644
--- a/src/main/java/org/olat/modules/lecture/LectureService.java
+++ b/src/main/java/org/olat/modules/lecture/LectureService.java
@@ -220,7 +220,7 @@ public interface LectureService {
 	 * 
 	 * @param block The block to delete.
 	 */
-	public void deleteLectureBlock(LectureBlock block);
+	public void deleteLectureBlock(LectureBlock block, Identity doer);
 	
 	/**
 	 * Delete all the lecture blocks and configuration of the specified course.
@@ -346,6 +346,8 @@ public interface LectureService {
 
 	public List<AbsenceNoticeInfos> searchAbsenceNotices(AbsenceNoticeSearchParameters searchParams);
 	
+	public List<AbsenceNotice> getAbsenceNoticeUniquelyRelatedTo(List<LectureBlock> blocks);
+	
 	/**
 	 * Detect an absence notice for the specified identity.
 	 * 
diff --git a/src/main/java/org/olat/modules/lecture/manager/AbsenceNoticeToLectureBlockDAO.java b/src/main/java/org/olat/modules/lecture/manager/AbsenceNoticeToLectureBlockDAO.java
index ef8a58fa273..d58b7019e37 100644
--- a/src/main/java/org/olat/modules/lecture/manager/AbsenceNoticeToLectureBlockDAO.java
+++ b/src/main/java/org/olat/modules/lecture/manager/AbsenceNoticeToLectureBlockDAO.java
@@ -19,8 +19,11 @@
  */
 package org.olat.modules.lecture.manager;
 
+import java.util.ArrayList;
 import java.util.Date;
+import java.util.HashSet;
 import java.util.List;
+import java.util.stream.Collectors;
 
 import org.olat.core.commons.persistence.DB;
 import org.olat.core.commons.persistence.QueryBuilder;
@@ -80,6 +83,42 @@ public class AbsenceNoticeToLectureBlockDAO {
 				.getResultList();
 	}
 	
+	/**
+	 * Return all relations of notices which has a relation with
+	 * the specified lecture blocks.
+	 * 
+	 * @param blocks A list of lecture blocks
+	 * @return A list of absence notice to lecture block relationship
+	 */
+	public List<AbsenceNoticeToLectureBlock> getRelationsAStepFurther(List<LectureBlock> blocks) {
+		if(blocks == null || blocks.isEmpty()) return new ArrayList<>();
+		
+		QueryBuilder sb = new QueryBuilder(255);
+		sb.append("select targetNoticeToBlock from absencenoticetolectureblock noticeToBlock")
+		  .append(" inner join noticeToBlock.absenceNotice as notice")
+		  .append(" inner join absencenoticetolectureblock as targetNoticeToBlock on (targetNoticeToBlock.absenceNotice.key=notice.key)")
+		  .append(" inner join fetch targetNoticeToBlock.absenceNotice as targetNotice")
+		  .append(" inner join fetch targetNoticeToBlock.lectureBlock as targetLectureBlock")
+		  .append(" where noticeToBlock.lectureBlock.key in (:lectureBlockKeys)");
+		
+		List<Long> lectureBlockKeys = blocks.stream()
+				.map(LectureBlock::getKey)
+				.collect(Collectors.toList());
+		List<AbsenceNoticeToLectureBlock> relations= dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), AbsenceNoticeToLectureBlock.class)
+				.setParameter("lectureBlockKeys", lectureBlockKeys)
+				.getResultList();
+		return new ArrayList<>(new HashSet<>(relations));
+	}
+	
+	public int deleteRelations(LectureBlock lectureBlock) {
+		String query = "delete from absencenoticetolectureblock noticeToBlock where noticeToBlock.lectureBlock.key=:lectureBlockKey";
+		return dbInstance.getCurrentEntityManager()
+				.createQuery(query)
+				.setParameter("lectureBlockKey", lectureBlock.getKey())
+				.executeUpdate();
+	}
+	
 	public void deleteRelations(List<AbsenceNoticeToLectureBlock> relations) {
 		for(AbsenceNoticeToLectureBlock relation:relations) {
 			deleteRelation(relation);
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 193d4f228ac..761ae338f64 100644
--- a/src/main/java/org/olat/modules/lecture/manager/LectureServiceImpl.java
+++ b/src/main/java/org/olat/modules/lecture/manager/LectureServiceImpl.java
@@ -33,6 +33,7 @@ import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.stream.Collectors;
 
 import org.apache.logging.log4j.Logger;
@@ -430,7 +431,7 @@ public class LectureServiceImpl implements LectureService, UserDataDeletable, De
 	}
 
 	@Override
-	public void deleteLectureBlock(LectureBlock lectureBlock) {
+	public void deleteLectureBlock(LectureBlock lectureBlock, Identity actingIdentity) {
 		//first remove events
 		LectureBlock reloadedBlock = lectureBlockDao.loadByKey(lectureBlock.getKey());
 		RepositoryEntry entry = reloadedBlock.getEntry();
@@ -442,7 +443,44 @@ public class LectureServiceImpl implements LectureService, UserDataDeletable, De
 			List<Identity> teachers = getTeachers(reloadedBlock);
 			unsyncInternalCalendar(reloadedBlock, teachers);
 		}
+		
+		List<AbsenceNotice> absenceNotices = getAbsenceNoticeUniquelyRelatedTo(Collections.singletonList(lectureBlock));
+		for(AbsenceNotice absenceNotice:absenceNotices) {
+			deleteAbsenceNotice(absenceNotice, actingIdentity);
+		}
+		absenceNoticeToLectureBlockDao.deleteRelations(reloadedBlock);
 		lectureBlockDao.delete(reloadedBlock);
+		dbInstance.commit();// make it quick
+	}
+
+	@Override
+	public List<AbsenceNotice> getAbsenceNoticeUniquelyRelatedTo(List<LectureBlock> blocks) {
+		List<AbsenceNoticeToLectureBlock> relations = absenceNoticeToLectureBlockDao.getRelationsAStepFurther(blocks);
+
+		Set<Long> lectureBlockKeys = blocks.stream()
+				.map(LectureBlock::getKey)
+				.collect(Collectors.toSet());
+		
+		Map<AbsenceNotice,AtomicInteger> counters = new HashMap<>();
+		for(AbsenceNoticeToLectureBlock relation:relations) {
+			AbsenceNotice notice = relation.getAbsenceNotice();
+			LectureBlock lectureBlock = relation.getLectureBlock();
+			
+			AtomicInteger counter = counters
+					.computeIfAbsent(notice, n -> new AtomicInteger(0));
+			if(!lectureBlockKeys.contains(lectureBlock.getKey())) {
+				counter.incrementAndGet();
+			}
+		}
+		
+		List<AbsenceNotice> uniquelyRelated = new ArrayList<>();
+		for(Map.Entry<AbsenceNotice,AtomicInteger> counterEntry:counters.entrySet()) {
+			int count = counterEntry.getValue().intValue();
+			if(count == 0) {
+				uniquelyRelated.add(counterEntry.getKey());
+			}
+		}
+		return uniquelyRelated;
 	}
 
 	@Override
diff --git a/src/main/java/org/olat/modules/lecture/restapi/LectureBlockWebService.java b/src/main/java/org/olat/modules/lecture/restapi/LectureBlockWebService.java
index 9ddbb25d460..b02ff0f483f 100644
--- a/src/main/java/org/olat/modules/lecture/restapi/LectureBlockWebService.java
+++ b/src/main/java/org/olat/modules/lecture/restapi/LectureBlockWebService.java
@@ -125,8 +125,8 @@ public class LectureBlockWebService {
 	 */
 	@DELETE
 	public Response deleteLectureBlock() {
-		lectureService.deleteLectureBlock(lectureBlock);
-		log.info(Tracing.M_AUDIT, "Lecture block deleted: " + lectureBlock);
+		lectureService.deleteLectureBlock(lectureBlock, null);
+		log.info(Tracing.M_AUDIT, "Lecture block deleted: {}", lectureBlock);
 		return Response.ok().build();
 	}
 
diff --git a/src/main/java/org/olat/modules/lecture/ui/ConfirmDeleteLectureBlockController.java b/src/main/java/org/olat/modules/lecture/ui/ConfirmDeleteLectureBlockController.java
new file mode 100644
index 00000000000..540fadc7fdb
--- /dev/null
+++ b/src/main/java/org/olat/modules/lecture/ui/ConfirmDeleteLectureBlockController.java
@@ -0,0 +1,171 @@
+/**
+ * <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.HashSet;
+import java.util.List;
+import java.util.Set;
+
+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.FormLink;
+import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement;
+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.FormLayoutContainer;
+import org.olat.core.gui.components.link.Link;
+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.logging.activity.CoreLoggingResourceable;
+import org.olat.core.logging.activity.LearningResourceLoggingAction;
+import org.olat.core.logging.activity.OlatResourceableType;
+import org.olat.core.logging.activity.ThreadLocalUserActivityLogger;
+import org.olat.core.util.StringHelper;
+import org.olat.modules.lecture.AbsenceNotice;
+import org.olat.modules.lecture.LectureBlock;
+import org.olat.modules.lecture.LectureBlockManagedFlag;
+import org.olat.modules.lecture.LectureService;
+import org.olat.user.UserManager;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 5 déc. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class ConfirmDeleteLectureBlockController extends FormBasicController {
+	
+	private static final String[] confirmKeys = new String[] { "confirm" };
+	
+	private FormLink deleteButton;
+	private MultipleSelectionElement confirmEl;
+	
+	private final List<LectureBlock> blocks;
+	
+	@Autowired
+	private UserManager userManager;
+	@Autowired
+	private LectureService lectureService;
+	
+	public ConfirmDeleteLectureBlockController(UserRequest ureq, WindowControl wControl, List<LectureBlock> blocks) {
+		super(ureq, wControl, "confirm_delete");
+		this.blocks = blocks;
+		initForm(ureq);
+	}
+	
+	public List<LectureBlock> getBlocks() {
+		return blocks;
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		if(formLayout instanceof FormLayoutContainer) {
+			StringBuilder titles = new StringBuilder(128);
+			for(LectureBlock block:blocks) {
+				if(titles.length() > 0) titles.append(", ");
+				titles.append(StringHelper.escapeHtml(block.getTitle()));
+			}
+			String text = translate("confirm.delete.lectures", new String[] { titles.toString() });
+			((FormLayoutContainer)formLayout).contextPut("msg", text);
+		}
+		
+		List<AbsenceNotice> notices = lectureService.getAbsenceNoticeUniquelyRelatedTo(blocks);
+		if(!notices.isEmpty()) {
+			// confirmation delete notices
+			Set<Identity> identities = new HashSet<>();
+			for(AbsenceNotice notice:notices) {
+				identities.add(notice.getIdentity());
+			}
+
+			StringBuilder names = new StringBuilder(128);
+			for(Identity identity:identities) {
+				String name = userManager.getUserDisplayName(identity);
+				if(names.length() > 0) names.append(", ");
+				names.append(name);
+			}
+
+			String text = translate("confirm.delete.lectures.notices", new String[] { Integer.toString(notices.size()),  names.toString() });
+			((FormLayoutContainer)formLayout).contextPut("noticeMsg", text);
+
+			String[] confirmationValues = new String[] {translate("confirm.delete.lectures.notices.confirmation") };
+			confirmEl = uifactory.addCheckboxesHorizontal("confirm", "confirm.delete.lectures.notices.confirmation", formLayout, confirmKeys, confirmationValues);
+		}
+		deleteButton = uifactory.addFormLink("delete", formLayout, Link.BUTTON);
+		uifactory.addFormCancelButton("cancel", formLayout, ureq, getWindowControl());
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+	
+	@Override
+	protected boolean validateFormLogic(UserRequest ureq) {
+		boolean allOk = super.validateFormLogic(ureq);
+		
+		if(confirmEl != null) {
+			confirmEl.clearError();
+			if(!confirmEl.isAtLeastSelected(1)) {
+				confirmEl.setErrorKey("form.legende.mandatory", null);
+				allOk &= false;
+			}
+		}
+		
+		return allOk;
+	}
+
+	@Override
+	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
+		if(deleteButton == source) {
+			if(validateFormLogic(ureq)) {
+				doDeleteLectureBlocks(ureq);
+			}
+		}
+		super.formInnerEvent(ureq, source, event);
+	}
+
+	@Override
+	protected void formCancelled(UserRequest ureq) {
+		fireEvent(ureq, Event.CANCELLED_EVENT);
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		//
+	}
+	
+	private void doDeleteLectureBlocks(UserRequest ureq) {
+		for(LectureBlock block:blocks) {
+			if(LectureBlockManagedFlag.isManaged(block, LectureBlockManagedFlag.delete)) {
+				continue;
+			}
+			lectureService.deleteLectureBlock(block, getIdentity());
+			logAudit("Lecture block deleted: " + block);
+			ThreadLocalUserActivityLogger.log(LearningResourceLoggingAction.LECTURE_BLOCK_DELETED, getClass(),
+					CoreLoggingResourceable.wrap(block, OlatResourceableType.lectureBlock, block.getTitle()));
+		}
+		showInfo("lecture.deleted");
+		fireEvent(ureq, Event.DONE_EVENT);
+	}
+}
diff --git a/src/main/java/org/olat/modules/lecture/ui/LectureListRepositoryController.java b/src/main/java/org/olat/modules/lecture/ui/LectureListRepositoryController.java
index 06f0bdb6288..dd48a58b6de 100644
--- a/src/main/java/org/olat/modules/lecture/ui/LectureListRepositoryController.java
+++ b/src/main/java/org/olat/modules/lecture/ui/LectureListRepositoryController.java
@@ -53,8 +53,6 @@ import org.olat.core.gui.control.WindowControl;
 import org.olat.core.gui.control.controller.BasicController;
 import org.olat.core.gui.control.generic.closablewrapper.CloseableCalloutWindowController;
 import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController;
-import org.olat.core.gui.control.generic.modal.DialogBoxController;
-import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory;
 import org.olat.core.gui.control.generic.wizard.Step;
 import org.olat.core.gui.control.generic.wizard.StepRunnerCallback;
 import org.olat.core.gui.control.generic.wizard.StepsMainRunController;
@@ -63,7 +61,6 @@ import org.olat.core.logging.activity.CoreLoggingResourceable;
 import org.olat.core.logging.activity.LearningResourceLoggingAction;
 import org.olat.core.logging.activity.OlatResourceableType;
 import org.olat.core.logging.activity.ThreadLocalUserActivityLogger;
-import org.olat.core.util.StringHelper;
 import org.olat.modules.lecture.LectureBlock;
 import org.olat.modules.lecture.LectureBlockAuditLog;
 import org.olat.modules.lecture.LectureBlockManagedFlag;
@@ -101,11 +98,10 @@ public class LectureListRepositoryController extends FormBasicController {
 	
 	private ToolsController toolsCtrl;
 	private CloseableModalController cmc;
-	private DialogBoxController deleteDialogCtrl;
 	private StepsMainRunController importBlockWizard;
-	private DialogBoxController bulkDeleteDialogCtrl;
 	private EditLectureBlockController editLectureCtrl;
 	private CloseableCalloutWindowController toolsCalloutCtrl;
+	private ConfirmDeleteLectureBlockController deleteLectureBlocksCtrl;
 
 	private int counter = 0;
 	private final RepositoryEntry entry;
@@ -269,34 +265,23 @@ public class LectureListRepositoryController extends FormBasicController {
 			if(event == Event.DONE_EVENT || event == Event.CHANGED_EVENT) {
 				loadModel();
 			}
-		} else if(deleteDialogCtrl == source) {
-			if (DialogBoxUIFactory.isYesEvent(event) || DialogBoxUIFactory.isOkEvent(event)) {
-				LectureBlockRow row = (LectureBlockRow)deleteDialogCtrl.getUserObject();
-				doDelete(row);
+		} else if(deleteLectureBlocksCtrl == source) {
+			if (event == Event.DONE_EVENT || event == Event.CHANGED_EVENT) {
 				loadModel();
-				cleanUp();
-			}
-		} else if(bulkDeleteDialogCtrl == source) {
-			if (DialogBoxUIFactory.isYesEvent(event) || DialogBoxUIFactory.isOkEvent(event)) {
-				@SuppressWarnings("unchecked")
-				List<LectureBlock> blocks = (List<LectureBlock>)bulkDeleteDialogCtrl.getUserObject();
-				doDelete(blocks);
-				loadModel();
-				cleanUp();
 			}
+			cmc.deactivate();
+			cleanUp();
 		}
 		super.event(ureq, source, event);
 	}
 	
 	private void cleanUp() {
-		removeAsListenerAndDispose(bulkDeleteDialogCtrl);
-		removeAsListenerAndDispose(deleteDialogCtrl);
+		removeAsListenerAndDispose(deleteLectureBlocksCtrl);
 		removeAsListenerAndDispose(editLectureCtrl);
 		removeAsListenerAndDispose(toolsCalloutCtrl);
 		removeAsListenerAndDispose(toolsCtrl);
 		removeAsListenerAndDispose(cmc);
-		bulkDeleteDialogCtrl = null;
-		deleteDialogCtrl = null;
+		deleteLectureBlocksCtrl = null;
 		toolsCalloutCtrl = null;
 		editLectureCtrl = null;
 		toolsCtrl = null;
@@ -391,45 +376,27 @@ public class LectureListRepositoryController extends FormBasicController {
 		if(blocks.isEmpty()) {
 			showWarning("error.atleastone.lecture");
 		} else {
-			StringBuilder titles = new StringBuilder();
-			for(LectureBlock block:blocks) {
-				if(titles.length() > 0) titles.append(", ");
-				titles.append(StringHelper.escapeHtml(block.getTitle()));
-			}
-			String text = translate("confirm.delete.lectures", new String[] { titles.toString() });
-			bulkDeleteDialogCtrl = activateYesNoDialog(ureq, translate("delete.lectures.title"), text, bulkDeleteDialogCtrl);
-			bulkDeleteDialogCtrl.setUserObject(blocks);
+			deleteLectureBlocksCtrl = new ConfirmDeleteLectureBlockController(ureq, getWindowControl(), blocks);
+			listenTo(deleteLectureBlocksCtrl);
+			
+			String title = translate("delete.lectures.title");
+			cmc = new CloseableModalController(getWindowControl(), "close", deleteLectureBlocksCtrl.getInitialComponent(), true, title);
+			listenTo(cmc);
+			cmc.activate();
 		}
 	}
 	
 	private void doConfirmDelete(UserRequest ureq, LectureBlockRow row) {
-		String text = translate("confirm.delete.lectures", new String[] { row.getLectureBlock().getTitle() });
-		deleteDialogCtrl = activateYesNoDialog(ureq, translate("delete.lectures.title"), text, deleteDialogCtrl);
-		deleteDialogCtrl.setUserObject(row);
-	}
-	
-	private void doDelete(LectureBlockRow row) {
-		if(LectureBlockManagedFlag.isManaged(row.getLectureBlock(), LectureBlockManagedFlag.delete)) return;
+		List<LectureBlock> blocks = Collections.singletonList(row.getLectureBlock());
+		deleteLectureBlocksCtrl = new ConfirmDeleteLectureBlockController(ureq, getWindowControl(), blocks);
+		listenTo(deleteLectureBlocksCtrl);
 		
-		LectureBlock lectureBlock = row.getLectureBlock();
-		lectureService.deleteLectureBlock(lectureBlock);
-		showInfo("lecture.deleted");
-		logAudit("Lecture block deleted: " + lectureBlock);
-		ThreadLocalUserActivityLogger.log(LearningResourceLoggingAction.LECTURE_BLOCK_DELETED, getClass(),
-				CoreLoggingResourceable.wrap(lectureBlock, OlatResourceableType.lectureBlock, lectureBlock.getTitle()));
-		
-	}
-	
-	private void doDelete(List<LectureBlock> blocks) {
-		for(LectureBlock block:blocks) {
-			lectureService.deleteLectureBlock(block);
-			logAudit("Lecture block deleted: " + block);
-			ThreadLocalUserActivityLogger.log(LearningResourceLoggingAction.LECTURE_BLOCK_DELETED, getClass(),
-					CoreLoggingResourceable.wrap(block, OlatResourceableType.lectureBlock, block.getTitle()));
-		}
-		showInfo("lecture.deleted");
+		String title = translate("delete.lectures.title");
+		cmc = new CloseableModalController(getWindowControl(), "close", deleteLectureBlocksCtrl.getInitialComponent(), true, title);
+		listenTo(cmc);
+		cmc.activate();
 	}
-	
+
 	private void doExportLog(UserRequest ureq, LectureBlockRow row) {
 		LectureBlock lectureBlock = lectureService.getLectureBlock(row);
 		List<LectureBlockAuditLog> auditLog = lectureService.getAuditLog(row);
diff --git a/src/main/java/org/olat/modules/lecture/ui/_content/confirm_delete.html b/src/main/java/org/olat/modules/lecture/ui/_content/confirm_delete.html
new file mode 100644
index 00000000000..82a35bdac8c
--- /dev/null
+++ b/src/main/java/org/olat/modules/lecture/ui/_content/confirm_delete.html
@@ -0,0 +1,15 @@
+<div class="o_warning" role="alert">$msg
+#if($r.isNotEmpty($noticeMsg))
+	<br><i class="o_icon o_icon-lg o_icon_important"> </i> $noticeMsg
+#end
+</div>
+#if($r.available("confirm"))
+	$r.render("confirm")
+	#if($f.hasError("confirm"))
+		$r.render("confirm_ERROR")
+	#end
+#end
+<div class="o_button_group">
+	$r.render("cancel")
+	$r.render("delete")
+</div>
\ No newline at end of file
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 bbbb3ff301f..3df1c8ce7e0 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
@@ -99,6 +99,8 @@ confirm.delete.dispensation=Wollen Sie die Dispens von "{0}" l\u00F6schen? Die P
 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?
+confirm.delete.lectures.notices=<strong>{0}</strong> Absenze(n) / Abmeldunge(n) f\u00FCr "<strong>{1}</strong>" die nur zu den Lektionbl\u00F6cke geh\u00F6ren werden mit gel\u00F6scht. 
+confirm.delete.lectures.notices.confirmation=Ich verstehe dass die Absenzen / Abmeldungen definitv gel\u00F6scht werden.
 confirm.delete.reason=Wollen Sie wirklich diese Begr\u00FCndung "{0}" l\u00F6schen?
 contact.teachers=Dozenten kontaktieren
 contact.teachers.list.name=Dozenten
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 9dfdfaf9cfc..17e573a88ea 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
@@ -99,6 +99,8 @@ confirm.delete.assessment.mode.text=Do you really want to delete the exam of thi
 confirm.delete.assessment.mode.title=Delete exam
 confirm.delete.dispensation=Do you really want to delete the dispensation of "{0}"? The person will be considered present.
 confirm.delete.lectures=Do you really want to delete these lectures "{0}"?
+confirm.delete.lectures.notices=<strong>{0}</strong> absences / dispensation for "<strong>{1}</strong>" which only linked to the lecture blocks will be deleted. 
+confirm.delete.lectures.notices.confirmation=I understand that the absences / dispensation will be definitively deleted.
 confirm.delete.reason=Do you really want to delete this reason "{0}"?
 contact.teachers=Contact teachers
 contact.teachers.list.name=Teachers
diff --git a/src/test/java/org/olat/modules/lecture/manager/AbsenceNoticeToLectureBlockDAOTest.java b/src/test/java/org/olat/modules/lecture/manager/AbsenceNoticeToLectureBlockDAOTest.java
index ca518b79c84..f1215ec75b5 100644
--- a/src/test/java/org/olat/modules/lecture/manager/AbsenceNoticeToLectureBlockDAOTest.java
+++ b/src/test/java/org/olat/modules/lecture/manager/AbsenceNoticeToLectureBlockDAOTest.java
@@ -19,6 +19,7 @@
  */
 package org.olat.modules.lecture.manager;
 
+import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Collections;
 import java.util.Date;
@@ -194,6 +195,35 @@ public class AbsenceNoticeToLectureBlockDAOTest extends OlatTestCase {
 		Assert.assertFalse(rollCallList.contains(oldRollCall1));
 	}
 	
+	@Test
+	public void getRelationsAStepFurther() {
+		
+		RepositoryEntry entry = JunitTestHelper.createAndPersistRepositoryEntry();
+		LectureBlock lectureBlockEntry1 = createMinimalLectureBlock(entry, new Date(), new Date());
+		LectureBlock lectureBlockEntry2 = createMinimalLectureBlock(entry, new Date(), new Date());
+		List<LectureBlock> lectureBlockEntries = new ArrayList<>();
+		lectureBlockEntries.add(lectureBlockEntry1);
+		lectureBlockEntries.add(lectureBlockEntry2);
+
+		Identity participant = JunitTestHelper.createAndPersistIdentityAsRndUser("absent-1");
+		AbsenceNotice notice = lectureService.createAbsenceNotice(participant, AbsenceNoticeType.absence, AbsenceNoticeTarget.entries,
+				CalendarUtils.startOfDay(new Date()), CalendarUtils.endOfDay(new Date()),
+				null, null, Boolean.TRUE, null, lectureBlockEntries, null);
+		dbInstance.commitAndCloseSession();
+		
+		List<AbsenceNoticeToLectureBlock> relations = absenceNoticeToLectureBlockDao
+				.getRelationsAStepFurther(Collections.singletonList(lectureBlockEntry1));
+		dbInstance.commitAndCloseSession();
+		
+		Assert.assertNotNull(relations);
+		Assert.assertEquals(2, relations.size());
+		
+		for(AbsenceNoticeToLectureBlock relation:relations) {
+			Assert.assertEquals(notice, relation.getAbsenceNotice());
+			Assert.assertTrue(lectureBlockEntries.remove(relation.getLectureBlock()));
+		}
+	}
+	
 	private LectureBlock createMinimalLectureBlock(RepositoryEntry entry, Date start, Date end) {
 		LectureBlock lectureBlock = lectureBlockDao.createLectureBlock(entry);
 		lectureBlock.setStartDate(start);
diff --git a/src/test/java/org/olat/modules/lecture/manager/LectureServiceTest.java b/src/test/java/org/olat/modules/lecture/manager/LectureServiceTest.java
index b1cecbe2a5a..38093ba9a39 100644
--- a/src/test/java/org/olat/modules/lecture/manager/LectureServiceTest.java
+++ b/src/test/java/org/olat/modules/lecture/manager/LectureServiceTest.java
@@ -501,6 +501,218 @@ public class LectureServiceTest extends OlatTestCase {
 		Assert.assertNull(reloadedRollCall3.getAbsenceNotice());
 	}
 	
+	@Test
+	public void getAbsenceNoticeUniquelyRelatedTo() {
+		Identity participant1 = JunitTestHelper.createAndPersistIdentityAsRndUser("noticee-1");
+		Identity participant2 = JunitTestHelper.createAndPersistIdentityAsRndUser("noticee-2");
+		RepositoryEntry entry1 = JunitTestHelper.createAndPersistRepositoryEntry();
+		RepositoryEntry entry2 = JunitTestHelper.createAndPersistRepositoryEntry();
+
+		LectureBlock block1 = createMinimalLectureBlock(entry1);
+		LectureBlock block2 = createMinimalLectureBlock(entry2);
+		LectureBlock block3 = createMinimalLectureBlock(entry2);
+		LectureBlock block4 = createMinimalLectureBlock(entry2);
+		dbInstance.commit();
+
+		List<LectureBlock> lectureBlocks = new ArrayList<>();
+		lectureBlocks.add(block1);
+		lectureBlocks.add(block2);
+
+		AbsenceNotice notice1_2 = lectureService.createAbsenceNotice(participant1, AbsenceNoticeType.notified, AbsenceNoticeTarget.lectureblocks,
+				new Date(), new Date(), null, null, null, null, lectureBlocks, null);
+		AbsenceNotice notice1_2_alt = lectureService.createAbsenceNotice(participant2, AbsenceNoticeType.notified, AbsenceNoticeTarget.lectureblocks,
+				new Date(), new Date(), null, null, null, null, lectureBlocks, null);
+		AbsenceNotice notice3 = lectureService.createAbsenceNotice(participant1, AbsenceNoticeType.notified, AbsenceNoticeTarget.lectureblocks,
+				new Date(), new Date(), null, null, null, null, Collections.singletonList(block3), null);
+		AbsenceNotice notice3b = lectureService.createAbsenceNotice(participant2, AbsenceNoticeType.notified, AbsenceNoticeTarget.lectureblocks,
+				new Date(), new Date(), null, null, null, null, Collections.singletonList(block3), null);
+		
+		List<LectureBlock> lectureSecondBlocks = new ArrayList<>();
+		lectureSecondBlocks.add(block1);
+		lectureSecondBlocks.add(block2);
+		lectureSecondBlocks.add(block4);
+		
+		AbsenceNotice notice4 = lectureService.createAbsenceNotice(participant2, AbsenceNoticeType.notified, AbsenceNoticeTarget.lectureblocks,
+				new Date(), new Date(), null, null, null, null, lectureSecondBlocks, null);
+		dbInstance.commit();
+		Assert.assertNotNull(notice4);
+		Assert.assertNotNull(notice1_2);
+		Assert.assertNotNull(notice1_2_alt);
+		
+		List<LectureBlock> allLectureBlocks = new ArrayList<>();
+		allLectureBlocks.add(block1);
+		allLectureBlocks.add(block3);
+		
+		// check block 1 + 3
+		List<AbsenceNotice> uniquelyRelatedNotices = lectureService.getAbsenceNoticeUniquelyRelatedTo(allLectureBlocks);
+		Assert.assertNotNull(uniquelyRelatedNotices);
+		Assert.assertEquals(2, uniquelyRelatedNotices.size());
+		Assert.assertTrue(uniquelyRelatedNotices.contains(notice3));
+		Assert.assertTrue(uniquelyRelatedNotices.contains(notice3b));
+
+		List<AbsenceNotice> uniquelyRelatedNoticesTo4 = lectureService.getAbsenceNoticeUniquelyRelatedTo(Collections.singletonList(block4));
+		Assert.assertNotNull(uniquelyRelatedNoticesTo4);
+		Assert.assertTrue(uniquelyRelatedNoticesTo4.isEmpty());
+		
+		// dummy check
+		List<AbsenceNotice> emptyList = lectureService.getAbsenceNoticeUniquelyRelatedTo(Collections.emptyList());
+		Assert.assertNotNull(emptyList);
+		Assert.assertTrue(emptyList.isEmpty());
+	}
+	
+	@Test
+	public void getAbsenceNoticeUniquelyRelatedTo_minimal() {
+		Identity participant = JunitTestHelper.createAndPersistIdentityAsRndUser("noticee-3");
+		RepositoryEntry entry = JunitTestHelper.createAndPersistRepositoryEntry();
+
+		LectureBlock block = createMinimalLectureBlock(entry);
+		dbInstance.commit();
+
+		AbsenceNotice noticeEntries = lectureService.createAbsenceNotice(participant, AbsenceNoticeType.notified, AbsenceNoticeTarget.allentries,
+				new Date(), new Date(), null, null, null, null, null, null);
+		AbsenceNotice noticeLectures = lectureService.createAbsenceNotice(participant, AbsenceNoticeType.notified, AbsenceNoticeTarget.lectureblocks,
+				new Date(), new Date(), null, null, null, null, Collections.singletonList(block), null);
+
+		// check
+		List<AbsenceNotice> uniquelyRelatedNotices = lectureService.getAbsenceNoticeUniquelyRelatedTo(Collections.singletonList(block));
+		Assert.assertNotNull(uniquelyRelatedNotices);
+		Assert.assertEquals(1, uniquelyRelatedNotices.size());
+		Assert.assertTrue(uniquelyRelatedNotices.contains(noticeLectures));
+		Assert.assertFalse(uniquelyRelatedNotices.contains(noticeEntries));
+	}
+	
+	/**
+	 * Lengthy test to check the method with more than a few notices
+	 * and cross check different cases.
+	 * 
+	 */
+	@Test
+	public void getAbsenceNoticeUniquelyRelatedTo_variant() {
+		Identity participant1 = JunitTestHelper.createAndPersistIdentityAsRndUser("noticee-1");
+		Identity participant2 = JunitTestHelper.createAndPersistIdentityAsRndUser("noticee-2");
+		Identity participant3 = JunitTestHelper.createAndPersistIdentityAsRndUser("noticee-3");
+		RepositoryEntry entry1 = JunitTestHelper.createAndPersistRepositoryEntry();
+		RepositoryEntry entry2 = JunitTestHelper.createAndPersistRepositoryEntry();
+
+		LectureBlock block1 = createMinimalLectureBlock(entry1);
+		LectureBlock block2 = createMinimalLectureBlock(entry1);
+		LectureBlock block3 = createMinimalLectureBlock(entry1);
+		LectureBlock block4 = createMinimalLectureBlock(entry2);
+		LectureBlock block5 = createMinimalLectureBlock(entry2);
+		LectureBlock block6 = createMinimalLectureBlock(entry2);
+		dbInstance.commit();
+
+		List<LectureBlock> lectureBlocks1_2 = new ArrayList<>();
+		lectureBlocks1_2.add(block1);
+		lectureBlocks1_2.add(block2);
+
+		AbsenceNotice notice1_2 = lectureService.createAbsenceNotice(participant1, AbsenceNoticeType.notified, AbsenceNoticeTarget.lectureblocks,
+				new Date(), new Date(), null, "1 2", null, null, lectureBlocks1_2, null);
+		
+		List<LectureBlock> lectureBlocks2_3_4 = new ArrayList<>();
+		lectureBlocks2_3_4.add(block2);
+		lectureBlocks2_3_4.add(block3);
+		lectureBlocks2_3_4.add(block4);
+		AbsenceNotice notice2_3_4 = lectureService.createAbsenceNotice(participant2, AbsenceNoticeType.notified, AbsenceNoticeTarget.lectureblocks,
+				new Date(), new Date(), null, "2 3 4", null, null, lectureBlocks2_3_4, null);
+		
+		List<LectureBlock> lectureBlocks1_3_4 = new ArrayList<>();
+		lectureBlocks1_3_4.add(block1);
+		lectureBlocks1_3_4.add(block3);
+		lectureBlocks1_3_4.add(block4);
+		AbsenceNotice notice1_3_4 = lectureService.createAbsenceNotice(participant3, AbsenceNoticeType.notified, AbsenceNoticeTarget.lectureblocks,
+				new Date(), new Date(), null, "1 3 4", null, null, lectureBlocks1_3_4, null);
+		
+		AbsenceNotice notice4 = lectureService.createAbsenceNotice(participant2, AbsenceNoticeType.notified, AbsenceNoticeTarget.lectureblocks,
+				new Date(), new Date(), null, "4", null, null, Collections.singletonList(block4), null);
+		
+		List<LectureBlock> lectureSecondBlocks4_5 = new ArrayList<>();
+		lectureSecondBlocks4_5.add(block4);
+		lectureSecondBlocks4_5.add(block5);
+		AbsenceNotice notice4_5 = lectureService.createAbsenceNotice(participant2, AbsenceNoticeType.notified, AbsenceNoticeTarget.lectureblocks,
+				new Date(), new Date(), null, "4 5", null, null, lectureSecondBlocks4_5, null);
+		
+		List<LectureBlock> lectureBlocks4_5_6 = new ArrayList<>();
+		lectureBlocks4_5_6.add(block4);
+		lectureBlocks4_5_6.add(block5);
+		lectureBlocks4_5_6.add(block6);
+		AbsenceNotice notice4_5_6 = lectureService.createAbsenceNotice(participant2, AbsenceNoticeType.notified, AbsenceNoticeTarget.lectureblocks,
+				new Date(), new Date(), null, "4 5 6", null, null, lectureBlocks4_5_6, null);
+		
+		AbsenceNotice notice6 = lectureService.createAbsenceNotice(participant2, AbsenceNoticeType.notified, AbsenceNoticeTarget.lectureblocks,
+				new Date(), new Date(), null, "6", null, null, Collections.singletonList(block6), null);
+		dbInstance.commit();
+		
+		
+		// check blocks 4, 5, 6
+		List<AbsenceNotice> uniquelyRelatedNotices4_5_6 = lectureService.getAbsenceNoticeUniquelyRelatedTo(lectureBlocks4_5_6);
+		Assert.assertNotNull(uniquelyRelatedNotices4_5_6);
+		Assert.assertEquals(4, uniquelyRelatedNotices4_5_6.size());
+		Assert.assertTrue(uniquelyRelatedNotices4_5_6.contains(notice4));
+		Assert.assertTrue(uniquelyRelatedNotices4_5_6.contains(notice4_5));
+		Assert.assertTrue(uniquelyRelatedNotices4_5_6.contains(notice4_5_6));
+		Assert.assertTrue(uniquelyRelatedNotices4_5_6.contains(notice6));
+		
+		// check blocks 1 5
+		List<LectureBlock> lectureBlocks1_5 = new ArrayList<>();
+		lectureBlocks1_5.add(block1);
+		lectureBlocks1_5.add(block3);
+		List<AbsenceNotice> uniquelyRelatedNotices1_5 = lectureService.getAbsenceNoticeUniquelyRelatedTo(lectureBlocks1_5);
+		Assert.assertNotNull(uniquelyRelatedNotices1_5);
+		Assert.assertTrue(uniquelyRelatedNotices1_5.isEmpty());
+		
+		// check blocks 1 2
+		lectureBlocks1_2.add(block1);// part of the test
+		lectureBlocks1_2.add(block2);
+		List<AbsenceNotice> uniquelyRelatedNotices1_2 = lectureService.getAbsenceNoticeUniquelyRelatedTo(lectureBlocks1_2);
+		Assert.assertNotNull(uniquelyRelatedNotices1_2);
+		Assert.assertEquals(1, uniquelyRelatedNotices1_2.size());
+		Assert.assertTrue(uniquelyRelatedNotices1_2.contains(notice1_2));
+		
+		// check blocks 4
+		List<LectureBlock> lectureBlocks4 = new ArrayList<>();
+		lectureBlocks4.add(block4);
+		List<AbsenceNotice> uniquelyRelatedNotices4 = lectureService.getAbsenceNoticeUniquelyRelatedTo(lectureBlocks4);
+		Assert.assertNotNull(uniquelyRelatedNotices4);
+		Assert.assertEquals(1, uniquelyRelatedNotices4.size());
+		Assert.assertTrue(uniquelyRelatedNotices4.contains(notice4));
+		
+		// check blocks 2, 3, 4, 5, 6
+		List<LectureBlock> lectureBlocks2_3_4_5_6 = new ArrayList<>();
+		lectureBlocks2_3_4_5_6.add(block2);
+		lectureBlocks2_3_4_5_6.add(block3);
+		lectureBlocks2_3_4_5_6.add(block4);
+		lectureBlocks2_3_4_5_6.add(block5);
+		lectureBlocks2_3_4_5_6.add(block6);
+		List<AbsenceNotice> uniquelyRelatedNotices2_3_4_5_6 = lectureService.getAbsenceNoticeUniquelyRelatedTo(lectureBlocks2_3_4_5_6);
+		Assert.assertNotNull(uniquelyRelatedNotices2_3_4_5_6);
+		Assert.assertEquals(5, uniquelyRelatedNotices2_3_4_5_6.size());
+		Assert.assertTrue(uniquelyRelatedNotices2_3_4_5_6.contains(notice2_3_4));
+		Assert.assertTrue(uniquelyRelatedNotices2_3_4_5_6.contains(notice4));
+		Assert.assertTrue(uniquelyRelatedNotices2_3_4_5_6.contains(notice4_5));
+		Assert.assertTrue(uniquelyRelatedNotices2_3_4_5_6.contains(notice4_5_6));
+		Assert.assertTrue(uniquelyRelatedNotices2_3_4_5_6.contains(notice6));
+		
+		// check all blocks
+		List<LectureBlock> lectureBlocks1_2_3_4_5_6 = new ArrayList<>();
+		lectureBlocks1_2_3_4_5_6.add(block1);
+		lectureBlocks1_2_3_4_5_6.add(block2);
+		lectureBlocks1_2_3_4_5_6.add(block3);
+		lectureBlocks1_2_3_4_5_6.add(block4);
+		lectureBlocks1_2_3_4_5_6.add(block5);
+		lectureBlocks1_2_3_4_5_6.add(block6);
+		List<AbsenceNotice> uniquelyRelatedNotices1_2_3_4_5_6 = lectureService.getAbsenceNoticeUniquelyRelatedTo(lectureBlocks1_2_3_4_5_6);
+		Assert.assertNotNull(uniquelyRelatedNotices1_2_3_4_5_6);
+		Assert.assertEquals(7, uniquelyRelatedNotices1_2_3_4_5_6.size());
+		Assert.assertTrue(uniquelyRelatedNotices1_2_3_4_5_6.contains(notice1_2));
+		Assert.assertTrue(uniquelyRelatedNotices1_2_3_4_5_6.contains(notice1_3_4));
+		Assert.assertTrue(uniquelyRelatedNotices1_2_3_4_5_6.contains(notice2_3_4));
+		Assert.assertTrue(uniquelyRelatedNotices1_2_3_4_5_6.contains(notice4));
+		Assert.assertTrue(uniquelyRelatedNotices1_2_3_4_5_6.contains(notice4_5));
+		Assert.assertTrue(uniquelyRelatedNotices1_2_3_4_5_6.contains(notice4_5_6));
+		Assert.assertTrue(uniquelyRelatedNotices1_2_3_4_5_6.contains(notice6));
+	}
+	
 	private LectureBlock createMinimalLectureBlock(RepositoryEntry entry) {
 		LectureBlock lectureBlock = lectureService.createLectureBlock(entry);
 		lectureBlock.setStartDate(new Date());
-- 
GitLab