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