From 44fd171685cbe736af32df16d30458db9b2af7cb Mon Sep 17 00:00:00 2001
From: srosse <none@none>
Date: Fri, 7 Apr 2017 11:30:43 +0200
Subject: [PATCH] OO-2636: database setup, overview list of participants with
 statistics and custom attendance rate editor

---
 .../course/run/CourseRuntimeController.java   |   2 +-
 .../lecture/LectureParticipantSummary.java    |   4 +
 .../olat/modules/lecture/LectureService.java  |  29 +++
 .../lecture/manager/LectureBlockDAO.java      |  10 +-
 .../manager/LectureBlockRollCallDAO.java      |  50 ++++
 .../manager/LectureParticipantSummaryDAO.java |  21 ++
 .../lecture/manager/LectureServiceImpl.java   |  28 ++-
 .../modules/lecture/manager/ReasonDAO.java    |   9 +
 .../lecture/model/LectureBlockImpl.java       |   2 +-
 .../model/LectureParticipantSummaryImpl.java  |  12 +-
 .../model/ParticipantLectureStatistics.java   |  78 ++++++
 .../ui/EditParticipantRateController.java     | 121 +++++++++
 .../ui/LectureRepositoryAdminController.java  |  22 +-
 .../lecture/ui/ParticipantListDataModel.java  | 100 ++++++++
 .../ParticipantListRepositoryController.java  | 232 ++++++++++++++++++
 .../modules/lecture/ui/ParticipantRow.java    |  50 ++++
 .../lecture/ui/TeacherOverviewController.java |  36 ++-
 .../lecture/ui/TeacherRollCallController.java | 212 +++++++++++++++-
 .../ui/_content/authorized_absence_cell.html  |  12 +-
 .../lecture/ui/_content/date_start_end.html   |   7 +-
 .../_content/participant_list_overview.html   |   1 +
 .../modules/lecture/ui/_content/rollcall.html |   1 +
 .../ui/_i18n/LocalStrings_de.properties       |  92 ++++---
 .../ui/_i18n/LocalStrings_en.properties       |  92 ++++---
 .../LectureBlockStatusCellRenderer.java       |  51 ++++
 .../LectureStatisticsCellRenderer.java        |  46 ++--
 .../_spring/userPropertiesContext.xml         |  23 ++
 .../database/mysql/alter_11_4_x_to_11_5_0.sql |   2 +-
 .../database/mysql/setupDatabase.sql          | 115 +++++++++
 .../postgresql/alter_11_4_x_to_11_5_0.sql     |   2 +-
 .../database/postgresql/setupDatabase.sql     | 119 +++++++++
 31 files changed, 1437 insertions(+), 144 deletions(-)
 create mode 100644 src/main/java/org/olat/modules/lecture/model/ParticipantLectureStatistics.java
 create mode 100644 src/main/java/org/olat/modules/lecture/ui/EditParticipantRateController.java
 create mode 100644 src/main/java/org/olat/modules/lecture/ui/ParticipantListDataModel.java
 create mode 100644 src/main/java/org/olat/modules/lecture/ui/ParticipantListRepositoryController.java
 create mode 100644 src/main/java/org/olat/modules/lecture/ui/ParticipantRow.java
 create mode 100644 src/main/java/org/olat/modules/lecture/ui/_content/participant_list_overview.html
 create mode 100644 src/main/java/org/olat/modules/lecture/ui/component/LectureBlockStatusCellRenderer.java

diff --git a/src/main/java/org/olat/course/run/CourseRuntimeController.java b/src/main/java/org/olat/course/run/CourseRuntimeController.java
index 5280fed1a03..fed10b48f37 100644
--- a/src/main/java/org/olat/course/run/CourseRuntimeController.java
+++ b/src/main/java/org/olat/course/run/CourseRuntimeController.java
@@ -1371,7 +1371,7 @@ public class CourseRuntimeController extends RepositoryEntryRuntimeController im
 
 				OLATResourceable ores = OresHelper.createOLATResourceableType("lecturesAdmin");
 				WindowControl swControl = addToHistory(ureq, ores, null);
-				LectureRepositoryAdminController ctrl = new LectureRepositoryAdminController(ureq, swControl, toolbarPanel, getRepositoryEntry());
+				LectureRepositoryAdminController ctrl = new LectureRepositoryAdminController(ureq, swControl, getRepositoryEntry());
 				lecturesAdminCtrl = pushController(ureq, translate("command.options.lectures.admin"), ctrl);
 				setActiveTool(lecturesAdminLink);
 				currentToolCtr = lecturesAdminCtrl;
diff --git a/src/main/java/org/olat/modules/lecture/LectureParticipantSummary.java b/src/main/java/org/olat/modules/lecture/LectureParticipantSummary.java
index effb2209700..a58af2911f2 100644
--- a/src/main/java/org/olat/modules/lecture/LectureParticipantSummary.java
+++ b/src/main/java/org/olat/modules/lecture/LectureParticipantSummary.java
@@ -31,4 +31,8 @@ import org.olat.core.id.ModifiedInfo;
 public interface LectureParticipantSummary extends CreateInfo, ModifiedInfo {
 
 	public Long getKey();
+	
+	public Double getAttendanceRate();
+	
+	public void setAttendanceRate(Double rate);
 }
diff --git a/src/main/java/org/olat/modules/lecture/LectureService.java b/src/main/java/org/olat/modules/lecture/LectureService.java
index 9adf3171b28..7a820f7e2de 100644
--- a/src/main/java/org/olat/modules/lecture/LectureService.java
+++ b/src/main/java/org/olat/modules/lecture/LectureService.java
@@ -26,6 +26,7 @@ import org.olat.basesecurity.IdentityRef;
 import org.olat.core.id.Identity;
 import org.olat.modules.lecture.model.LectureBlockAndRollCall;
 import org.olat.modules.lecture.model.LectureStatistics;
+import org.olat.modules.lecture.model.ParticipantLectureStatistics;
 import org.olat.repository.RepositoryEntry;
 import org.olat.repository.RepositoryEntryRef;
 
@@ -85,6 +86,14 @@ public interface LectureService {
 	 */
 	public List<Reason> getAllReasons();
 	
+	/**
+	 * Load a reason by its primary key.
+	 * 
+	 * @param key The primary key
+	 * @return A reason
+	 */
+	public Reason getReason(Long key);
+	
 	/**
 	 * 
 	 * @param title
@@ -218,6 +227,18 @@ public interface LectureService {
 	
 	public void removeTeacher(LectureBlock block, Identity teacher);
 	
+	/**
+	 * The method will not set the date of admission.
+	 * 
+	 * @param entry
+	 * @param identity
+	 * @return
+	 */
+	public LectureParticipantSummary getOrCreateParticipantSummary(RepositoryEntry entry, Identity identity);
+	
+	
+	public LectureParticipantSummary saveParticipantSummary(LectureParticipantSummary summary);
+	
 	
 	/**
 	 * Returns the statistics for the specified participant.
@@ -227,6 +248,14 @@ public interface LectureService {
 	 */
 	public List<LectureStatistics> getParticipantLecturesStatistics(IdentityRef identity);
 	
+	/**
+	 * Return all the statistics for a course / repository entry.
+	 * 
+	 * @param entry The course / repository entry
+	 * @return Statistics per user
+	 */
+	public List<ParticipantLectureStatistics> getParticipantsLecturesStatistics(RepositoryEntryRef entry);
+	
 	/**
 	 * 
 	 * @param entry
diff --git a/src/main/java/org/olat/modules/lecture/manager/LectureBlockDAO.java b/src/main/java/org/olat/modules/lecture/manager/LectureBlockDAO.java
index 596b82cb391..77ee05f5305 100644
--- a/src/main/java/org/olat/modules/lecture/manager/LectureBlockDAO.java
+++ b/src/main/java/org/olat/modules/lecture/manager/LectureBlockDAO.java
@@ -75,10 +75,14 @@ public class LectureBlockDAO {
 	}
 	
 	public LectureBlock loadByKey(Long key) {
-		String q = "select block from lectureblock block where block.key=:blockKey";
-		
+		StringBuilder sb = new StringBuilder();
+		sb.append("select block from lectureblock block")
+		  .append(" left join fetch block.reasonEffectiveEnd reason")
+		  .append(" inner join fetch block.entry entry")
+		  .append(" where block.key=:blockKey");
+
 		List<LectureBlock> blocks = dbInstance.getCurrentEntityManager()
-				.createQuery(q, LectureBlock.class)
+				.createQuery(sb.toString(), LectureBlock.class)
 				.setParameter("blockKey", key)
 				.getResultList();
 		return blocks == null || blocks.isEmpty() ? null : blocks.get(0);
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 53e34bb770e..b6ee28fc843 100644
--- a/src/main/java/org/olat/modules/lecture/manager/LectureBlockRollCallDAO.java
+++ b/src/main/java/org/olat/modules/lecture/manager/LectureBlockRollCallDAO.java
@@ -36,6 +36,7 @@ import org.olat.modules.lecture.LectureBlockRollCall;
 import org.olat.modules.lecture.model.LectureBlockAndRollCall;
 import org.olat.modules.lecture.model.LectureBlockRollCallImpl;
 import org.olat.modules.lecture.model.LectureStatistics;
+import org.olat.modules.lecture.model.ParticipantLectureStatistics;
 import org.olat.repository.RepositoryEntryRef;
 import org.olat.user.UserManager;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -280,4 +281,53 @@ public class LectureBlockRollCallDAO {
 		
 		return new ArrayList<>(stats.values());
 	}
+	
+	
+	public List<ParticipantLectureStatistics> getStatistics(RepositoryEntryRef entry) {
+		StringBuilder sb = new StringBuilder();
+		sb.append("select ident.key, ")
+		  .append(" count(call.lecturesAttendedNumber), count(call.lecturesAbsentNumber),")
+		  .append(" count(block.plannedLecturesNumber), count(block.effectiveLecturesNumber)")
+		  .append(" from lectureblock block")
+		  .append(" inner join block.groups blockToGroup")
+		  .append(" inner join blockToGroup.group bGroup")
+		  .append(" inner join bGroup.members membership")
+		  .append(" inner join membership.identity ident")
+		  .append(" left join lectureblockrollcall as call on (call.identity.key=membership.identity.key and call.lectureBlock.key=block.key)")
+		  .append(" where block.entry.key=:entryKey and membership.role='").append(GroupRoles.participant.name()).append("'")
+		  .append(" group by ident.key");
+		
+		List<Object[]> rawObjects = dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), Object[].class)
+				.setParameter("entryKey", entry.getKey())
+				.getResultList();
+		Map<Long,ParticipantLectureStatistics> stats = new HashMap<>();
+		for(Object[] rawObject:rawObjects) {
+			int pos = 0;//jump roll call key
+			Long identityKey = (Long)rawObject[pos++];
+			Long attended = PersistenceHelper.extractLong(rawObject, pos++);
+			Long absent = PersistenceHelper.extractLong(rawObject, pos++);
+			Long plannedBlocks = PersistenceHelper.extractLong(rawObject, pos++);
+			
+			ParticipantLectureStatistics entryStatistics;
+			if(stats.containsKey(identityKey)) {
+				entryStatistics = stats.get(identityKey);
+			} else {
+				entryStatistics = new ParticipantLectureStatistics(identityKey);
+				stats.put(identityKey, entryStatistics);
+			}
+			
+			if(absent != null) {
+				entryStatistics.addTotalAbsentLectures(absent.longValue());
+			}
+			if(attended != null) {
+				entryStatistics.addTotalAttendedLectures(attended.longValue());
+			}
+			if(plannedBlocks != null) {
+				entryStatistics.addTotalPlannedLectures(plannedBlocks.longValue());
+			}
+		}
+		
+		return new ArrayList<>(stats.values());
+	}
 }
diff --git a/src/main/java/org/olat/modules/lecture/manager/LectureParticipantSummaryDAO.java b/src/main/java/org/olat/modules/lecture/manager/LectureParticipantSummaryDAO.java
index 3ab15a3914e..9db1bbe5971 100644
--- a/src/main/java/org/olat/modules/lecture/manager/LectureParticipantSummaryDAO.java
+++ b/src/main/java/org/olat/modules/lecture/manager/LectureParticipantSummaryDAO.java
@@ -24,6 +24,7 @@ import java.util.Date;
 import java.util.List;
 
 import org.olat.basesecurity.GroupRoles;
+import org.olat.basesecurity.IdentityRef;
 import org.olat.core.commons.persistence.DB;
 import org.olat.core.id.Identity;
 import org.olat.modules.lecture.LectureBlock;
@@ -31,6 +32,7 @@ import org.olat.modules.lecture.LectureParticipantSummary;
 import org.olat.modules.lecture.model.LectureParticipantSummaryImpl;
 import org.olat.modules.lecture.model.ParticipantAndLectureSummary;
 import org.olat.repository.RepositoryEntry;
+import org.olat.repository.RepositoryEntryRef;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
@@ -84,4 +86,23 @@ public class LectureParticipantSummaryDAO {
 		}
 		return summaries;
 	}
+	
+	public LectureParticipantSummary getSummary(RepositoryEntryRef entry, IdentityRef identity) {
+		StringBuilder sb = new StringBuilder();
+		sb.append("select summary from lectureparticipantsummary summary")
+		  .append(" inner join fetch summary.identity ident")
+		  .append(" inner join fetch ident.user identUser")
+		  .append(" where summary.entry.key=:entryKey and ident.key=:identityKey");
+		
+		List<LectureParticipantSummary> summaries = dbInstance.getCurrentEntityManager()
+			.createQuery(sb.toString(), LectureParticipantSummary.class)
+			.setParameter("entryKey", entry.getKey())
+			.setParameter("identityKey", identity.getKey())
+			.getResultList();
+		return summaries == null || summaries.isEmpty() ? null : summaries.get(0);
+	}
+	
+	public LectureParticipantSummary update(LectureParticipantSummary summary) {
+		return dbInstance.getCurrentEntityManager().merge(summary);
+	}
 }
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 891f7528424..758167e99d6 100644
--- a/src/main/java/org/olat/modules/lecture/manager/LectureServiceImpl.java
+++ b/src/main/java/org/olat/modules/lecture/manager/LectureServiceImpl.java
@@ -35,6 +35,7 @@ import org.olat.modules.lecture.LectureBlock;
 import org.olat.modules.lecture.LectureBlockRef;
 import org.olat.modules.lecture.LectureBlockRollCall;
 import org.olat.modules.lecture.LectureBlockToGroup;
+import org.olat.modules.lecture.LectureParticipantSummary;
 import org.olat.modules.lecture.LectureService;
 import org.olat.modules.lecture.Reason;
 import org.olat.modules.lecture.RepositoryEntryLectureConfiguration;
@@ -42,6 +43,7 @@ import org.olat.modules.lecture.model.LectureBlockAndRollCall;
 import org.olat.modules.lecture.model.LectureBlockImpl;
 import org.olat.modules.lecture.model.LectureStatistics;
 import org.olat.modules.lecture.model.ParticipantAndLectureSummary;
+import org.olat.modules.lecture.model.ParticipantLectureStatistics;
 import org.olat.repository.RepositoryEntry;
 import org.olat.repository.RepositoryEntryRef;
 import org.olat.repository.manager.RepositoryEntryDAO;
@@ -142,6 +144,11 @@ public class LectureServiceImpl implements LectureService {
 		return reasonDao.getReasons();
 	}
 
+	@Override
+	public Reason getReason(Long key) {
+		return reasonDao.loadReason(key);
+	}
+
 	@Override
 	public Reason createReason(String title, String description) {
 		return reasonDao.createReason(title, description);
@@ -282,12 +289,31 @@ public class LectureServiceImpl implements LectureService {
 		LectureBlockImpl block = (LectureBlockImpl)lectureBlock;
 		groupDao.removeMembership(block.getTeacherGroup(), teacher);
 	}
-	
+
+	@Override
+	public LectureParticipantSummary getOrCreateParticipantSummary(RepositoryEntry entry, Identity identity) {
+		LectureParticipantSummary summary = lectureParticipantSummaryDao.getSummary(entry, identity);
+		if(summary == null) {
+			summary = lectureParticipantSummaryDao.createSummary(entry, identity, null);
+		}
+		return summary;
+	}
+
+	@Override
+	public LectureParticipantSummary saveParticipantSummary(LectureParticipantSummary summary) {
+		return lectureParticipantSummaryDao.update(summary);
+	}
+
 	@Override
 	public List<LectureStatistics> getParticipantLecturesStatistics(IdentityRef identity) {
 		return lectureBlockRollCallDao.getStatistics(identity);
 	}
 
+	@Override
+	public List<ParticipantLectureStatistics> getParticipantsLecturesStatistics(RepositoryEntryRef entry) {
+		return lectureBlockRollCallDao.getStatistics(entry);
+	}
+
 	@Override
 	public List<LectureBlockAndRollCall> getParticipantLectureBlocks(RepositoryEntryRef entry, IdentityRef participant) {
 		return lectureBlockRollCallDao.getParticipantLectureBlockAndRollCalls(entry, participant);
diff --git a/src/main/java/org/olat/modules/lecture/manager/ReasonDAO.java b/src/main/java/org/olat/modules/lecture/manager/ReasonDAO.java
index 651b70a80a9..27d8d7ca27b 100644
--- a/src/main/java/org/olat/modules/lecture/manager/ReasonDAO.java
+++ b/src/main/java/org/olat/modules/lecture/manager/ReasonDAO.java
@@ -54,6 +54,15 @@ public class ReasonDAO {
 		return dbInstance.getCurrentEntityManager().merge(reason);
 	}
 	
+	public Reason loadReason(Long key) {
+		String sb = "select reason from lecturereason reason where reason.key=:reasonKey";
+		List<Reason> reasons = dbInstance.getCurrentEntityManager()
+				.createQuery(sb, Reason.class)
+				.setParameter("reasonKey", key)
+				.getResultList();
+		return reasons == null || reasons.isEmpty() ? null : reasons.get(0);
+	}
+	
 	public List<Reason> getReasons() {
 		String sb = "select reason from lecturereason reason";
 		return dbInstance.getCurrentEntityManager()
diff --git a/src/main/java/org/olat/modules/lecture/model/LectureBlockImpl.java b/src/main/java/org/olat/modules/lecture/model/LectureBlockImpl.java
index 2e28492cdb9..71b3b1cb2d1 100644
--- a/src/main/java/org/olat/modules/lecture/model/LectureBlockImpl.java
+++ b/src/main/java/org/olat/modules/lecture/model/LectureBlockImpl.java
@@ -108,7 +108,7 @@ public class LectureBlockImpl implements Persistable, LectureBlock {
 	@Column(name="l_roll_call_status", nullable=false, insertable=true, updatable=true)
 	private String rollCallStatusString;
 	
-	@ManyToOne(targetEntity=RepositoryEntry.class,fetch=FetchType.LAZY,optional=true)
+	@ManyToOne(targetEntity=ReasonImpl.class,fetch=FetchType.LAZY,optional=true)
 	@JoinColumn(name="fk_reason", nullable=true, insertable=true, updatable=true)
 	private Reason reasonEffectiveEnd;
 	
diff --git a/src/main/java/org/olat/modules/lecture/model/LectureParticipantSummaryImpl.java b/src/main/java/org/olat/modules/lecture/model/LectureParticipantSummaryImpl.java
index 8b8c8544868..8c9fd9cd2bf 100644
--- a/src/main/java/org/olat/modules/lecture/model/LectureParticipantSummaryImpl.java
+++ b/src/main/java/org/olat/modules/lecture/model/LectureParticipantSummaryImpl.java
@@ -63,8 +63,8 @@ public class LectureParticipantSummaryImpl implements Persistable, LecturePartic
 	@Column(name="lastmodified", nullable=false, insertable=true, updatable=true)
 	private Date lastModified;
 
-	@Column(name="l_quota", nullable=true, insertable=true, updatable=true)
-	private Double quota;
+	@Column(name="l_attendance_rate", nullable=true, insertable=true, updatable=true)
+	private Double attendanceRate;
 	@Column(name="l_first_admission_date", nullable=true, insertable=true, updatable=true)
 	private Date firstAdmissionDate;
 	@Column(name="l_attended_lectures", nullable=false, insertable=true, updatable=true)
@@ -107,12 +107,12 @@ public class LectureParticipantSummaryImpl implements Persistable, LecturePartic
 		this.lastModified = lastModified;
 	}
 
-	public Double getQuota() {
-		return quota;
+	public Double getAttendanceRate() {
+		return attendanceRate;
 	}
 
-	public void setQuota(Double quota) {
-		this.quota = quota;
+	public void setAttendanceRate(Double attendanceRate) {
+		this.attendanceRate = attendanceRate;
 	}
 
 	public Date getFirstAdmissionDate() {
diff --git a/src/main/java/org/olat/modules/lecture/model/ParticipantLectureStatistics.java b/src/main/java/org/olat/modules/lecture/model/ParticipantLectureStatistics.java
new file mode 100644
index 00000000000..e8dee444467
--- /dev/null
+++ b/src/main/java/org/olat/modules/lecture/model/ParticipantLectureStatistics.java
@@ -0,0 +1,78 @@
+/**
+ * <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;
+
+/**
+ * 
+ * Initial date: 7 avr. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class ParticipantLectureStatistics {
+	
+	private final Long identityKey;
+	
+	private long totalLectureBlocks;
+	private long totalPlannedLectures = 0l;
+	private long totalAttendedLectures = 0l;
+	private long totalAbsentLectures = 0l;
+	
+	public ParticipantLectureStatistics(Long identityKey) {
+		this.identityKey = identityKey;
+	}
+
+	public Long getIdentityKey() {
+		return identityKey;
+	}
+
+	public long getTotalPlannedLectures() {
+		return totalPlannedLectures;
+	}
+	
+	public void addTotalPlannedLectures(long lectures) {
+		totalPlannedLectures += lectures;
+	}
+
+	public long getTotalAttendedLectures() {
+		return totalAttendedLectures;
+	}
+	
+	public void addTotalAttendedLectures(long lectures) {
+		totalAttendedLectures += lectures;
+	}
+
+	public long getTotalAbsentLectures() {
+		return totalAbsentLectures;
+	}
+	
+	public void addTotalAbsentLectures(long lectures) {
+		totalAbsentLectures += lectures;
+	}
+
+	public long getTotalLectureBlocks() {
+		return totalLectureBlocks;
+	}
+
+	public void addTotalLectureBlocks(long lectures) {
+		totalLectureBlocks += lectures;
+	}
+	
+	
+}
diff --git a/src/main/java/org/olat/modules/lecture/ui/EditParticipantRateController.java b/src/main/java/org/olat/modules/lecture/ui/EditParticipantRateController.java
new file mode 100644
index 00000000000..e5d304dd246
--- /dev/null
+++ b/src/main/java/org/olat/modules/lecture/ui/EditParticipantRateController.java
@@ -0,0 +1,121 @@
+package org.olat.modules.lecture.ui;
+
+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.TextElement;
+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.util.StringHelper;
+import org.olat.modules.lecture.LectureParticipantSummary;
+import org.olat.modules.lecture.LectureService;
+import org.olat.repository.RepositoryEntry;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 6 avr. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class EditParticipantRateController extends FormBasicController {
+	
+	private TextElement rateEl;
+	private FormLink removeCustomRateButton;
+	
+	private double defaultRate;
+	private LectureParticipantSummary participantSummary;
+	
+	@Autowired
+	private LectureService lectureService;
+
+	public EditParticipantRateController(UserRequest ureq, WindowControl wControl, RepositoryEntry entry, Identity identity, double defaultRate) {
+		super(ureq, wControl);
+		this.defaultRate = defaultRate;
+		participantSummary = lectureService.getOrCreateParticipantSummary(entry, identity);
+		initForm(ureq);
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		long rate = Math.round(defaultRate * 100.0d);
+		uifactory.addStaticTextElement("entry.rate", Long.toString(rate) + "%", formLayout);
+		
+		String customRate = "";
+		if(participantSummary.getAttendanceRate() != null) {
+			long cRate = Math.round(participantSummary.getAttendanceRate().doubleValue() * 100.0d);
+			customRate = Long.toString(cRate);
+		}
+		rateEl = uifactory.addTextElement("participant.rate", "participant.rate", 4, customRate, formLayout);
+		
+		FormLayoutContainer buttonsCont = FormLayoutContainer.createButtonLayout("buttons", getTranslator());
+		formLayout.add(buttonsCont);
+		uifactory.addFormCancelButton("cancel", buttonsCont, ureq, getWindowControl());
+		removeCustomRateButton = uifactory.addFormLink("remove.custom.rate", "remove.custom.rate", null, buttonsCont, Link.BUTTON);
+		uifactory.addFormSubmitButton("save", buttonsCont);
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+	@Override
+	protected boolean validateFormLogic(UserRequest ureq) {
+		boolean allOk = true;
+		
+		rateEl.clearError();
+		if(StringHelper.containsNonWhitespace(rateEl.getValue())) {
+			try {
+				int val = Integer.parseInt(rateEl.getValue());
+				if(val < 0 && val > 100) {
+					rateEl.setErrorKey("error.integer.between", new String[] {"0", "100"});
+				}
+			} catch (NumberFormatException e) {
+				rateEl.setErrorKey("form.error.nointeger", null);
+				allOk &= false;
+			}
+		}
+		
+		return allOk & super.validateFormLogic(ureq);
+	}
+
+	@Override
+	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
+		if(removeCustomRateButton == source) {
+			doRemoveCustomRate(ureq);
+		}
+		super.formInnerEvent(ureq, source, event);
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		String customRate = rateEl.getValue();
+		if(StringHelper.containsNonWhitespace(customRate)) {
+			double val = Long.parseLong(customRate) / 100.0d;
+			participantSummary.setAttendanceRate(val);
+		} else {
+			participantSummary.setAttendanceRate(null);
+		}
+		participantSummary = lectureService.saveParticipantSummary(participantSummary);
+		fireEvent(ureq, Event.DONE_EVENT);
+	}
+
+	@Override
+	protected void formCancelled(UserRequest ureq) {
+		fireEvent(ureq, Event.CANCELLED_EVENT);
+	}
+	
+	private void doRemoveCustomRate(UserRequest ureq) {
+		participantSummary.setAttendanceRate(null);
+		participantSummary = lectureService.saveParticipantSummary(participantSummary);
+		fireEvent(ureq, Event.DONE_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 c70eece6d58..a068dbaccaa 100644
--- a/src/main/java/org/olat/modules/lecture/ui/LectureRepositoryAdminController.java
+++ b/src/main/java/org/olat/modules/lecture/ui/LectureRepositoryAdminController.java
@@ -26,7 +26,6 @@ import org.olat.core.gui.components.link.LinkFactory;
 import org.olat.core.gui.components.segmentedview.SegmentViewComponent;
 import org.olat.core.gui.components.segmentedview.SegmentViewEvent;
 import org.olat.core.gui.components.segmentedview.SegmentViewFactory;
-import org.olat.core.gui.components.stack.TooledStackedPanel;
 import org.olat.core.gui.components.velocity.VelocityContainer;
 import org.olat.core.gui.control.Event;
 import org.olat.core.gui.control.WindowControl;
@@ -43,24 +42,25 @@ public class LectureRepositoryAdminController extends BasicController {
 	
 	private final VelocityContainer mainVC;
 	private final SegmentViewComponent segmentView;
-	private final Link lecturesLink, settingsLink;
-	private final TooledStackedPanel toolbarPanel;
+	private final Link lecturesLink, settingsLink, participantsLink;
 	
 	private LectureListRepositoryController lecturesCtrl;
 	private LectureRepositorySettingsController settingsCtrl;
+	private ParticipantListRepositoryController participantsCtrl;
 	
 	private RepositoryEntry entry;
 	
-	public LectureRepositoryAdminController(UserRequest ureq, WindowControl wControl, TooledStackedPanel toolbarPanel, RepositoryEntry entry) {
+	public LectureRepositoryAdminController(UserRequest ureq, WindowControl wControl, RepositoryEntry entry) {
 		super(ureq, wControl);
 		this.entry = entry;
-		this.toolbarPanel = toolbarPanel;
 		
 		mainVC = createVelocityContainer("admin_repository");
 		
 		segmentView = SegmentViewFactory.createSegmentView("segments", mainVC, this);
-		lecturesLink = LinkFactory.createLink("repo.lectures", mainVC, this);
+		lecturesLink = LinkFactory.createLink("repo.lectures.block", mainVC, this);
 		segmentView.addSegment(lecturesLink, true);
+		participantsLink = LinkFactory.createLink("repo.participants", mainVC, this);
+		segmentView.addSegment(participantsLink, false);
 		settingsLink = LinkFactory.createLink("repo.settings", mainVC, this);
 		segmentView.addSegment(settingsLink, false);
 		
@@ -84,6 +84,8 @@ public class LectureRepositoryAdminController extends BasicController {
 					doOpenLectures(ureq);
 				} else if (clickedLink == settingsLink){
 					doOpenSettings(ureq);
+				} else if(clickedLink == participantsLink) {
+					doOpenParticipants(ureq);
 				}
 			}
 		}
@@ -104,4 +106,12 @@ public class LectureRepositoryAdminController extends BasicController {
 		}
 		mainVC.put("segmentCmp", settingsCtrl.getInitialComponent());
 	}
+	
+	private void doOpenParticipants(UserRequest ureq) {
+		if(participantsCtrl == null) {
+			participantsCtrl = new ParticipantListRepositoryController(ureq, getWindowControl(), entry);
+			listenTo(participantsCtrl);
+		}
+		mainVC.put("segmentCmp", participantsCtrl.getInitialComponent());
+	}
 }
\ No newline at end of file
diff --git a/src/main/java/org/olat/modules/lecture/ui/ParticipantListDataModel.java b/src/main/java/org/olat/modules/lecture/ui/ParticipantListDataModel.java
new file mode 100644
index 00000000000..05b6dc3023f
--- /dev/null
+++ b/src/main/java/org/olat/modules/lecture/ui/ParticipantListDataModel.java
@@ -0,0 +1,100 @@
+/**
+ * <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.DefaultFlexiTableDataModel;
+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;
+
+/**
+ * 
+ * Initial date: 6 avr. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class ParticipantListDataModel extends DefaultFlexiTableDataModel<ParticipantRow>
+implements SortableFlexiTableDataModel<ParticipantRow> {
+	
+	private final Locale locale;
+	
+	public ParticipantListDataModel(FlexiTableColumnModel columnModel, Locale locale) {
+		super(columnModel);
+		this.locale = locale;
+	}
+
+	@Override
+	public void sort(SortKey sortKey) {
+		//
+	}
+	
+	@Override
+	public Object getValueAt(int row, int col) {
+		ParticipantRow participant = getObject(row);
+		return getValueAt(participant, col);
+	}
+
+	@Override
+	public Object getValueAt(ParticipantRow row, int col) {
+		if(col < ParticipantListRepositoryController.USER_PROPS_OFFSET) {
+			switch(ParticipantsCols.values()[col]) {
+				case username: return row.getIdentityName();
+				case progress: return row.getStatistics();
+				default: return null;
+			}
+		}
+		int propPos = col - ParticipantListRepositoryController.USER_PROPS_OFFSET;
+		return row.getIdentityProp(propPos);
+	}
+
+	@Override
+	public DefaultFlexiTableDataModel<ParticipantRow> createCopyWithEmptyList() {
+		return new ParticipantListDataModel(getTableColumnModel(), locale);
+	}
+	
+	public enum ParticipantsCols implements FlexiSortableColumnDef {
+		username("table.header.username"),
+		progress("table.header.progress");
+		
+		private final String i18nKey;
+		
+		private ParticipantsCols(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/ParticipantListRepositoryController.java b/src/main/java/org/olat/modules/lecture/ui/ParticipantListRepositoryController.java
new file mode 100644
index 00000000000..d88208283d9
--- /dev/null
+++ b/src/main/java/org/olat/modules/lecture/ui/ParticipantListRepositoryController.java
@@ -0,0 +1,232 @@
+/**
+ * <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 java.util.Map;
+import java.util.stream.Collectors;
+
+import org.olat.basesecurity.BaseSecurity;
+import org.olat.basesecurity.BaseSecurityModule;
+import org.olat.basesecurity.GroupRoles;
+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.impl.FormBasicController;
+import org.olat.core.gui.components.form.flexible.impl.FormEvent;
+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.Identity;
+import org.olat.core.id.Roles;
+import org.olat.modules.lecture.LectureModule;
+import org.olat.modules.lecture.LectureService;
+import org.olat.modules.lecture.RepositoryEntryLectureConfiguration;
+import org.olat.modules.lecture.model.ParticipantLectureStatistics;
+import org.olat.modules.lecture.ui.ParticipantListDataModel.ParticipantsCols;
+import org.olat.modules.lecture.ui.component.LectureStatisticsCellRenderer;
+import org.olat.repository.RepositoryEntry;
+import org.olat.repository.RepositoryService;
+import org.olat.user.UserManager;
+import org.olat.user.propertyhandlers.UserPropertyHandler;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 6 avr. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class ParticipantListRepositoryController extends FormBasicController {
+	
+	protected static final String USER_PROPS_ID = ParticipantListRepositoryController.class.getCanonicalName();
+
+	public static final int USER_PROPS_OFFSET = 500;
+	
+	private final boolean isAdministrativeUser;
+	private List<UserPropertyHandler> userPropertyHandlers;
+	
+	private FlexiTableElement tableEl;
+	private ParticipantListDataModel tableModel;
+
+	private CloseableModalController cmc;
+	private EditParticipantRateController editRateCtrl;
+	
+	private final double defaultRate;
+	private final boolean rateEnabled;
+	private final boolean rollCallEnabled;
+	
+	private final RepositoryEntry entry;
+	private RepositoryEntryLectureConfiguration lectureConfig;
+	
+	@Autowired
+	private UserManager userManager;
+	@Autowired
+	private LectureModule lectureModule;
+	@Autowired
+	private LectureService lectureService;
+	@Autowired
+	private BaseSecurityModule securityModule;
+	@Autowired
+	private RepositoryService repositoryService;
+	@Autowired
+	private BaseSecurity securityManager;
+	
+	public ParticipantListRepositoryController(UserRequest ureq, WindowControl wControl, RepositoryEntry entry) {
+		super(ureq, wControl, "participant_list_overview");
+		this.entry = entry;
+		setTranslator(userManager.getPropertyHandlerTranslator(getTranslator()));
+		
+		Roles roles = ureq.getUserSession().getRoles();
+		isAdministrativeUser = securityModule.isUserAllowedAdminProps(roles);
+		userPropertyHandlers = userManager.getUserPropertyHandlersFor(USER_PROPS_ID, isAdministrativeUser);
+		
+		lectureConfig = lectureService.getRepositoryEntryLectureConfiguration(entry);
+		if(lectureConfig.isOverrideModuleDefault()) {
+			rateEnabled = lectureConfig.getCalculateAttendanceRate() == null ?
+					lectureModule.isRollCallCalculateAttendanceRateDefaultEnabled() : lectureConfig.getCalculateAttendanceRate().booleanValue();
+			defaultRate = lectureConfig.getRequiredAttendanceRate() == null ?
+					lectureModule.getRequiredAttendanceRateDefault() : lectureConfig.getRequiredAttendanceRate().doubleValue();
+			rollCallEnabled	= lectureConfig.getRollCallEnabled() == null ?
+					lectureModule.isRollCallDefaultEnabled() : lectureConfig.getRollCallEnabled();		
+		} else {
+			rateEnabled = lectureModule.isRollCallCalculateAttendanceRateDefaultEnabled();
+			defaultRate = lectureModule.getRequiredAttendanceRateDefault();
+			rollCallEnabled = lectureModule.isRollCallDefaultEnabled();
+		}
+
+		initForm(ureq);
+		loadModel();
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel();
+		if(isAdministrativeUser) {
+			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ParticipantsCols.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++;
+		}
+
+		if(rollCallEnabled) {
+			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ParticipantsCols.progress, new LectureStatisticsCellRenderer()));
+		}
+		if(rateEnabled) {
+			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel("edit", translate("edit"), "edit"));
+		}
+		
+		tableModel = new ParticipantListDataModel(columnsModel, getLocale()); 
+		tableEl = uifactory.addTableElement(getWindowControl(), "table", tableModel, 20, false, getTranslator(), formLayout);
+		tableEl.setExportEnabled(true);
+		tableEl.setMultiSelect(true);
+		tableEl.setSelectAllEnable(true);
+	}
+	
+	private void loadModel() {
+		List<Identity> participants = repositoryService.getMembers(entry, GroupRoles.participant.name());
+		List<ParticipantLectureStatistics> statistics = lectureService.getParticipantsLecturesStatistics(entry);
+		Map<Long, ParticipantLectureStatistics> identityToStatisticsMap = statistics.stream().collect(Collectors.toMap(s -> s.getIdentityKey(), s -> s));
+		
+		List<ParticipantRow> rows = new ArrayList<>(participants.size());
+		for(Identity participant:participants) {
+			ParticipantLectureStatistics stats = identityToStatisticsMap.get(participant.getKey());
+			rows.add(new ParticipantRow(participant, stats, userPropertyHandlers, getLocale()));
+		}
+		tableModel.setObjects(rows);
+		tableEl.reset(false, false, true);
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+	
+	@Override
+	protected void event(UserRequest ureq, Controller source, Event event) {
+		if(editRateCtrl == source) {
+			if(event == Event.DONE_EVENT) {
+				loadModel();
+			}
+			cmc.deactivate();
+			cleanUp();
+		} else if (cmc == source) {
+			cleanUp();
+		}
+		super.event(ureq, source, event);
+	}
+	
+	private void cleanUp() {
+		removeAsListenerAndDispose(editRateCtrl);
+		removeAsListenerAndDispose(cmc);
+		editRateCtrl = null;
+		cmc = null;
+	}
+
+	@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();
+				if("edit".equals(cmd)) {
+					ParticipantRow row = tableModel.getObject(se.getIndex());
+					doEdit(ureq, row);
+				}
+			}
+		}
+		super.formInnerEvent(ureq, source, event);
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		//
+	}
+	
+	private void doEdit(UserRequest ureq, ParticipantRow row) {
+		if(editRateCtrl != null) return;
+		
+		Identity identity = securityManager.loadIdentityByKey(row.getIdentityKey());
+		editRateCtrl = new EditParticipantRateController(ureq, getWindowControl(), entry, identity, defaultRate);
+		listenTo(editRateCtrl);
+		
+		String title = translate("edit.participant.rate");
+		cmc = new CloseableModalController(getWindowControl(), "close", editRateCtrl.getInitialComponent(), true, title, true);
+		listenTo(cmc);
+		cmc.activate();
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/modules/lecture/ui/ParticipantRow.java b/src/main/java/org/olat/modules/lecture/ui/ParticipantRow.java
new file mode 100644
index 00000000000..68fb2c6f514
--- /dev/null
+++ b/src/main/java/org/olat/modules/lecture/ui/ParticipantRow.java
@@ -0,0 +1,50 @@
+/**
+ * <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.core.id.Identity;
+import org.olat.modules.lecture.model.ParticipantLectureStatistics;
+import org.olat.user.UserPropertiesRow;
+import org.olat.user.propertyhandlers.UserPropertyHandler;
+
+/**
+ * Holder for the participant list
+ * 
+ * 
+ * Initial date: 6 avr. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class ParticipantRow extends UserPropertiesRow {
+	
+	private final ParticipantLectureStatistics statistics;
+	
+	public ParticipantRow(Identity identity, ParticipantLectureStatistics statistics, List<UserPropertyHandler> propertyHandlers, Locale locale) {
+		super(identity, propertyHandlers, locale);
+		this.statistics = statistics;
+	}
+
+	public ParticipantLectureStatistics getStatistics() {
+		return statistics;
+	}
+}
diff --git a/src/main/java/org/olat/modules/lecture/ui/TeacherOverviewController.java b/src/main/java/org/olat/modules/lecture/ui/TeacherOverviewController.java
index 024dceedff0..276ac3509e5 100644
--- a/src/main/java/org/olat/modules/lecture/ui/TeacherOverviewController.java
+++ b/src/main/java/org/olat/modules/lecture/ui/TeacherOverviewController.java
@@ -40,6 +40,7 @@ import org.olat.core.gui.components.form.flexible.impl.elements.table.TimeFlexiC
 import org.olat.core.gui.components.link.Link;
 import org.olat.core.gui.components.stack.TooledStackedPanel;
 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;
@@ -49,6 +50,7 @@ import org.olat.modules.lecture.LectureModule;
 import org.olat.modules.lecture.LectureService;
 import org.olat.modules.lecture.RepositoryEntryLectureConfiguration;
 import org.olat.modules.lecture.ui.TeacherOverviewDataModel.TeachCols;
+import org.olat.modules.lecture.ui.component.LectureBlockStatusCellRenderer;
 import org.olat.repository.RepositoryEntry;
 import org.springframework.beans.factory.annotation.Autowired;
 
@@ -95,7 +97,7 @@ public class TeacherOverviewController extends FormBasicController {
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(TeachCols.startTime, new TimeFlexiCellRenderer(getLocale())));
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(TeachCols.endTime, new TimeFlexiCellRenderer(getLocale())));
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(TeachCols.lectureBlock));
-		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(TeachCols.status));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(TeachCols.status, new LectureBlockStatusCellRenderer(getTranslator())));
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(TeachCols.details.i18nHeaderKey(), TeachCols.details.ordinal(), "details",
 				new BooleanCellRenderer(new StaticFlexiCellRenderer(translate("table.header.details"), "details"), null)));
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(TeachCols.export.i18nHeaderKey(), TeachCols.export.ordinal(), "export",
@@ -108,7 +110,7 @@ public class TeacherOverviewController extends FormBasicController {
 	private void loadModel() {
 		List<LectureBlock> blocks = lectureService.getLectureBlocks(entry, getIdentity());
 		tableModel.setObjects(blocks);
-		tableEl.reset(true, true, true);
+		tableEl.reset(false, false, true);
 
 		//reset
 		startButton.setVisible(false);
@@ -143,6 +145,17 @@ public class TeacherOverviewController extends FormBasicController {
 		//
 	}
 
+	@Override
+	protected void event(UserRequest ureq, Controller source, Event event) {
+		if(rollCallCtrl == source) {
+			if(event == Event.DONE_EVENT) {
+				toolbarPanel.popController(rollCallCtrl);
+				loadModel();
+			}
+		}
+		super.event(ureq, source, event);
+	}
+
 	@Override
 	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
 		if(source == tableEl) {
@@ -167,22 +180,25 @@ public class TeacherOverviewController extends FormBasicController {
 	}
 	
 	private void doSelectLectureBlock(UserRequest ureq, LectureBlock block) {
+		LectureBlock reloadedBlock = lectureService.getLectureBlock(block);
+		
 		boolean editable = false;
-		if(block.getStatus().equals(LectureBlockStatus.active)
-				|| block.getStatus().equals(LectureBlockStatus.partiallydone)) {
+		if(reloadedBlock.getStatus().equals(LectureBlockStatus.active)
+				|| reloadedBlock.getStatus().equals(LectureBlockStatus.partiallydone)) {
 			editable = true;
 		}
-		List<Identity> participants = lectureService.startLectureBlock(getIdentity(), block);
-		rollCallCtrl = new TeacherRollCallController(ureq, getWindowControl(), block, participants, editable);
+		List<Identity> participants = lectureService.startLectureBlock(getIdentity(), reloadedBlock);
+		rollCallCtrl = new TeacherRollCallController(ureq, getWindowControl(), reloadedBlock, participants, editable);
 		listenTo(rollCallCtrl);
-		toolbarPanel.pushController(block.getTitle(), rollCallCtrl);
+		toolbarPanel.pushController(reloadedBlock.getTitle(), rollCallCtrl);
 	}
 	
 	//same as above???
 	private void doStartRollCall(UserRequest ureq, LectureBlock block) {
-		List<Identity> participants = lectureService.startLectureBlock(getIdentity(), block);
-		rollCallCtrl = new TeacherRollCallController(ureq, getWindowControl(), block, participants, true);
+		LectureBlock reloadedBlock = lectureService.getLectureBlock(block);
+		List<Identity> participants = lectureService.startLectureBlock(getIdentity(), reloadedBlock);
+		rollCallCtrl = new TeacherRollCallController(ureq, getWindowControl(), reloadedBlock, participants, true);
 		listenTo(rollCallCtrl);
-		toolbarPanel.pushController(block.getTitle(), rollCallCtrl);
+		toolbarPanel.pushController(reloadedBlock.getTitle(), rollCallCtrl);
 	}
 }
diff --git a/src/main/java/org/olat/modules/lecture/ui/TeacherRollCallController.java b/src/main/java/org/olat/modules/lecture/ui/TeacherRollCallController.java
index af4303e9f6f..0760cd64dfd 100644
--- a/src/main/java/org/olat/modules/lecture/ui/TeacherRollCallController.java
+++ b/src/main/java/org/olat/modules/lecture/ui/TeacherRollCallController.java
@@ -20,7 +20,9 @@
 package org.olat.modules.lecture.ui;
 
 import java.util.ArrayList;
+import java.util.Calendar;
 import java.util.Collections;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -32,6 +34,7 @@ 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.FormLink;
 import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement;
+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.FormEvent;
@@ -51,8 +54,10 @@ import org.olat.core.id.Roles;
 import org.olat.core.util.StringHelper;
 import org.olat.modules.lecture.LectureBlock;
 import org.olat.modules.lecture.LectureBlockRollCall;
+import org.olat.modules.lecture.LectureBlockStatus;
 import org.olat.modules.lecture.LectureModule;
 import org.olat.modules.lecture.LectureService;
+import org.olat.modules.lecture.Reason;
 import org.olat.modules.lecture.ui.TeacherRollCallDataModel.RollCols;
 import org.olat.user.UserManager;
 import org.olat.user.propertyhandlers.UserPropertyHandler;
@@ -76,13 +81,16 @@ public class TeacherRollCallController extends FormBasicController {
 	
 	private FlexiTableElement tableEl;
 	private TeacherRollCallDataModel tableModel;
+	private TextElement blokcCommentEl;
+	private SingleSelection statusEl, effectiveEndReasonEl;
+	private TextElement effectiveEndHourEl, effectiveEndMinuteEl;
 	
 	private ReasonController reasonCtrl;
 	private CloseableCalloutWindowController reasonCalloutCtrl;
 	
 	private int counter = 0;
 	private final boolean editable;
-	private final LectureBlock lectureBlock;
+	private LectureBlock lectureBlock;
 	private final boolean isAdministrativeUser;
 	private final boolean autorizedAbsenceEnabled;
 	private List<UserPropertyHandler> userPropertyHandlers;
@@ -118,6 +126,94 @@ public class TeacherRollCallController extends FormBasicController {
 
 	@Override
 	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		// form for the lecture block
+		FormLayoutContainer blockCont = FormLayoutContainer.createDefaultFormLayout("block", getTranslator());
+		blockCont.setRootForm(mainForm);
+		formLayout.add("block", blockCont);
+		
+		uifactory.addStaticTextElement("lecture.title", lectureBlock.getTitle(), blockCont);
+		
+		String[] statusKeys = getAvailableStatus();
+		String[] statusValues = new String[statusKeys.length];
+		for(int i=statusKeys.length; i-->0; ) {
+			statusValues[i] = translate(statusKeys[i]);
+		}
+		statusEl = uifactory.addDropdownSingleselect("status", "lecture.block.status", blockCont, statusKeys, statusValues, null);
+		boolean statusFound = false;
+		if(lectureBlock.getStatus() != null) {
+			String lectureBlockStatus = lectureBlock.getStatus().name();
+			for(int i=statusKeys.length; i-->0; ) {
+				if(lectureBlockStatus.equals(statusKeys[i])) {
+					statusEl.select(statusKeys[i], true);
+					statusFound = true;
+					break;
+				}
+			}
+		}
+		if(!statusFound) {
+			statusEl.select(statusKeys[0], true);
+		}
+
+		String datePage = velocity_root + "/date_start_end.html";
+		FormLayoutContainer dateCont = FormLayoutContainer.createCustomFormLayout("start_end", getTranslator(), datePage);
+		dateCont.setLabel("lecture.block.effective.end", null);
+		blockCont.add(dateCont);
+		
+		effectiveEndHourEl = uifactory.addTextElement("lecture.end.hour", null, 2, "", dateCont);
+		effectiveEndHourEl.setDomReplacementWrapperRequired(false);
+		effectiveEndHourEl.setDisplaySize(2);
+		effectiveEndHourEl.setEnabled(editable);
+		effectiveEndMinuteEl = uifactory.addTextElement("lecture.end.minute", null, 2, "", dateCont);
+		effectiveEndMinuteEl.setDomReplacementWrapperRequired(false);
+		effectiveEndMinuteEl.setDisplaySize(2);
+		effectiveEndMinuteEl.setEnabled(editable);
+		if(lectureBlock != null) {
+			Calendar cal = Calendar.getInstance();
+			if(lectureBlock.getEffectiveEndDate() != null) {
+				cal.setTime(lectureBlock.getEffectiveEndDate());
+			} else if(lectureBlock.getEndDate() != null) {
+				cal.setTime(lectureBlock.getEndDate());
+			}
+			int hour = cal.get(Calendar.HOUR_OF_DAY);
+			int minute = cal.get(Calendar.MINUTE);
+			effectiveEndHourEl.setValue(Integer.toString(hour));
+			effectiveEndMinuteEl.setValue(Integer.toString(minute));
+		}
+		
+		List<String> reasonKeyList = new ArrayList<>();
+		List<String> reasonValueList = new ArrayList<>();
+		reasonKeyList.add("-");
+		reasonValueList.add("-");
+		
+		List<Reason> allReasons = lectureService.getAllReasons();
+		for(Reason reason:allReasons) {
+			reasonKeyList.add(reason.getKey().toString());
+			reasonValueList.add(reason.getTitle());
+		}
+		effectiveEndReasonEl = uifactory.addDropdownSingleselect("effective.reason", "lecture.block.effective.reason", blockCont,
+				reasonKeyList.toArray(new String[reasonKeyList.size()]), reasonValueList.toArray(new String[reasonValueList.size()]), null);
+		effectiveEndReasonEl.setEnabled(editable);
+		boolean found = false;
+		if(lectureBlock.getReasonEffectiveEnd() != null) {
+			String selectedReasonKey = lectureBlock.getReasonEffectiveEnd().getKey().toString();
+			for(String reasonKey:reasonKeyList) {
+				if(reasonKey.equals(selectedReasonKey)) {
+					effectiveEndReasonEl.select(reasonKey, true);
+					found = true;
+					break;
+				}
+			}
+		}
+		if(!found) {
+			effectiveEndReasonEl.select(reasonKeyList.get(0), true);
+		}
+
+		String blockComment = lectureBlock.getComment();
+		blokcCommentEl = uifactory.addTextElement("block.comment", "lecture.block.comment", 256, blockComment, blockCont);
+		blokcCommentEl.setEnabled(editable);
+		
+		// table
+		
 		FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel();
 		if(isAdministrativeUser) {
 			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(RollCols.username));
@@ -154,6 +250,19 @@ public class TeacherRollCallController extends FormBasicController {
 		uifactory.addFormSubmitButton("save", "save", formLayout);
 	}
 	
+	private String[] getAvailableStatus() {
+		List<String> statusList = new ArrayList<>();
+		statusList.add(LectureBlockStatus.active.name());
+		if(lectureModule.isStatusPartiallyDoneEnabled()) {
+			statusList.add(LectureBlockStatus.partiallydone.name());
+		}
+		statusList.add(LectureBlockStatus.done.name());
+		if(lectureModule.isStatusCancelledEnabled()) {
+			statusList.add(LectureBlockStatus.cancelled.name());
+		}
+		return statusList.toArray(new String[statusList.size()]);
+	}
+	
 	private void loadModel() {
 		List<LectureBlockRollCall> rollCalls = lectureService.getRollCalls(lectureBlock);
 		Map<Identity,LectureBlockRollCall> rollCallMap = new HashMap<>();
@@ -230,6 +339,7 @@ public class TeacherRollCallController extends FormBasicController {
 		String commentId = "comment_".concat(Integer.toString(++counter));
 		TextElement commentEl = uifactory.addTextElement(commentId, commentId, null, 128, comment, flc);
 		commentEl.setDomReplacementWrapperRequired(false);
+		commentEl.setEnabled(editable);
 		row.setCommentEl(commentEl);
 		flc.add(commentEl);
 		return row;
@@ -302,21 +412,73 @@ public class TeacherRollCallController extends FormBasicController {
 	protected boolean validateFormLogic(UserRequest ureq) {
 		boolean allOk = true;
 		
-		for(int i=tableModel.getRowCount(); i-->0; ) {
-			TeacherRollCallRow row = tableModel.getObject(i);
-			if(row.getRollCall() == null) {
-				//??? stop?
-			} else {
-				String reason = row.getRollCall().getAbsenceReason();
-				if(row.getAuthorizedAbsence().isAtLeastSelected(1) && !StringHelper.containsNonWhitespace(reason)) {
-					row.getAuthorizedAbsence().setErrorKey("error.reason.mandatory", null);
-					allOk &= false;
+		
+		boolean fullValidation = false;
+		if(!statusEl.isOneSelected()) {
+			statusEl.setErrorKey("form.legende.mandatory", null);
+			allOk &= false;
+		} else {
+			fullValidation = LectureBlockStatus.done.name().equals(statusEl.getSelectedKey());
+		}
+		
+		//block form
+		if(StringHelper.containsNonWhitespace(effectiveEndHourEl.getValue())
+				|| StringHelper.containsNonWhitespace(effectiveEndMinuteEl.getValue())) {
+			allOk &= validateInt(effectiveEndHourEl, 24, fullValidation);
+			allOk &= validateInt(effectiveEndMinuteEl, 60, fullValidation);
+			
+			if(fullValidation && (!effectiveEndReasonEl.isOneSelected() || effectiveEndReasonEl.isSelected(0))) {
+				effectiveEndReasonEl.setErrorKey("error.reason.mandatory", null);
+				allOk &= false;
+			}
+		} else if(fullValidation) {
+			effectiveEndHourEl.setErrorKey("form.legende.mandatory", null);
+			allOk &= false;
+		}
+		
+		// table
+		if(fullValidation) {
+			for(int i=tableModel.getRowCount(); i-->0; ) {
+				TeacherRollCallRow row = tableModel.getObject(i);
+				row.getAuthorizedAbsence().clearError();
+				
+				if(row.getRollCall() == null) {
+					//??? stop?
+				} else {
+					String reason = row.getRollCall().getAbsenceReason();
+					if(row.getAuthorizedAbsence().isAtLeastSelected(1) && !StringHelper.containsNonWhitespace(reason)) {
+						row.getAuthorizedAbsence().setErrorKey("error.reason.mandatory", null);
+						allOk &= false;
+					}
 				}
 			}
 		}
 
 		return allOk & super.validateFormLogic(ureq);
 	}
+	
+	private boolean validateInt(TextElement element, int max, boolean mandatory) {
+		boolean allOk = true;
+		
+		element.clearError();
+		if(StringHelper.containsNonWhitespace(element.getValue())) {
+			try {
+				int val = Integer.parseInt(element.getValue());
+				if(val < 0 || val > max) {
+					element.setErrorKey("error.integer.between", new String[] { "0", Integer.toString(max)} );
+					allOk &= false;
+				}
+			} catch (NumberFormatException e) {
+				element.setErrorKey("error.integer.between", new String[] { "0", Integer.toString(max)} );
+				allOk &= false;
+			}
+		} else if(mandatory) {
+			element.setErrorKey("form.legende.mandatory", null);
+			allOk &= false;
+		}
+		
+		return allOk;
+	}
 
 	@Override
 	protected void formOK(UserRequest ureq) {
@@ -337,6 +499,35 @@ public class TeacherRollCallController extends FormBasicController {
 					comment, absences);
 			row.setRollCall(rollCall);
 		}
+
+		lectureBlock = lectureService.getLectureBlock(lectureBlock);
+		lectureBlock.setComment(blokcCommentEl.getValue());
+		lectureBlock.setStatus(LectureBlockStatus.valueOf(statusEl.getSelectedKey()));
+		Date effectiveEndDate = getEffectiveEndDate();
+		if(effectiveEndDate == null) {
+			lectureBlock.setReasonEffectiveEnd(null);
+		} else {
+			lectureBlock.setEffectiveEndDate(effectiveEndDate);
+			Long reasonKey = new Long(effectiveEndReasonEl.getSelectedKey());
+			Reason selectedReason = lectureService.getReason(reasonKey);
+			lectureBlock.setReasonEffectiveEnd(selectedReason);
+		}
+
+		lectureBlock = lectureService.save(lectureBlock, null);
+		fireEvent(ureq, Event.DONE_EVENT);
+	}
+	
+	private Date getEffectiveEndDate() {
+		Date effectiveEndDate = null;
+		if(StringHelper.containsNonWhitespace(effectiveEndHourEl.getValue())
+				&& StringHelper.containsNonWhitespace(effectiveEndMinuteEl.getValue())) {
+			Calendar cal = Calendar.getInstance();
+			cal.setTime(lectureBlock.getStartDate());
+			cal.set(Calendar.HOUR_OF_DAY, Integer.parseInt(effectiveEndHourEl.getValue()));
+			cal.set(Calendar.MINUTE, Integer.parseInt(effectiveEndMinuteEl.getValue()));
+			effectiveEndDate = cal.getTime();
+		}
+		return effectiveEndDate;
 	}
 	
 	private void doCheckAllRow(TeacherRollCallRow row) {
@@ -376,6 +567,7 @@ public class TeacherRollCallController extends FormBasicController {
 		}
 		row.getReasonLink().setVisible(authorized);
 		row.getAuthorizedAbsenceCont().setDirty(true);
+		row.getAuthorizedAbsence().clearError();
 		row.setRollCall(rollCall);
 	}
 	
diff --git a/src/main/java/org/olat/modules/lecture/ui/_content/authorized_absence_cell.html b/src/main/java/org/olat/modules/lecture/ui/_content/authorized_absence_cell.html
index b3af0b72bf6..ab10fa851fd 100644
--- a/src/main/java/org/olat/modules/lecture/ui/_content/authorized_absence_cell.html
+++ b/src/main/java/org/olat/modules/lecture/ui/_content/authorized_absence_cell.html
@@ -1,6 +1,6 @@
-<div id="$r.getCId()" class="o_lecture_authorized_absence">
-$r.render($row.getAuthorizedAbsence()) $r.render($row.getReasonLink())
-</div>
-#if($f.hasError($row.getAuthorizedAbsence().getComponent().getComponentName()))
-	<div>$r.render("${row.getAuthorizedAbsence().getComponent().getComponentName()}_ERROR")</div>
-#end
\ No newline at end of file
+<div id="$r.getCId()">
+	<div class="o_lecture_authorized_absence">$r.render($row.getAuthorizedAbsence()) $r.render($row.getReasonLink())</div>
+	#if($f.hasError($row.getAuthorizedAbsence().getComponent().getComponentName()))
+		<div>$r.render("${row.getAuthorizedAbsence().getComponent().getComponentName()}_ERROR")</div>
+	#end
+</div>
\ No newline at end of file
diff --git a/src/main/java/org/olat/modules/lecture/ui/_content/date_start_end.html b/src/main/java/org/olat/modules/lecture/ui/_content/date_start_end.html
index 52dc9851c71..f154680ba12 100644
--- a/src/main/java/org/olat/modules/lecture/ui/_content/date_start_end.html
+++ b/src/main/java/org/olat/modules/lecture/ui/_content/date_start_end.html
@@ -1,2 +1,7 @@
 <div class="o_date_ms form-inline">$r.render("lecture.end.hour"):$r.render("lecture.end.minute")</div>
-
+#if($f.hasError("lecture.end.hour"))
+	<div>$r.render("lecture.end.hour_ERROR")</div>
+#end
+#if($f.hasError("lecture.end.minute"))
+	<div>$r.render("lecture.end.minute_ERROR")</div>
+#end
\ No newline at end of file
diff --git a/src/main/java/org/olat/modules/lecture/ui/_content/participant_list_overview.html b/src/main/java/org/olat/modules/lecture/ui/_content/participant_list_overview.html
new file mode 100644
index 00000000000..bade9402acd
--- /dev/null
+++ b/src/main/java/org/olat/modules/lecture/ui/_content/participant_list_overview.html
@@ -0,0 +1 @@
+$r.render("table")
\ No newline at end of file
diff --git a/src/main/java/org/olat/modules/lecture/ui/_content/rollcall.html b/src/main/java/org/olat/modules/lecture/ui/_content/rollcall.html
index f7103ee974e..61b75caefa3 100644
--- a/src/main/java/org/olat/modules/lecture/ui/_content/rollcall.html
+++ b/src/main/java/org/olat/modules/lecture/ui/_content/rollcall.html
@@ -1,3 +1,4 @@
+$r.render("block")
 $r.render("table")
 <div class="o_button_group">
 	$r.render("save")
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 f6b01dadc2d..755227bc6c5 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
@@ -1,48 +1,76 @@
-#Thu Sep 03 11:09:03 CEST 2015
+#Thu Apr 06 19:05:04 CEST 2017
+active=Aktiv
 add.lecture=Neue Lektion
 add.reason=Begr\u00FCndung erstellen
 admin.menu.title=Lektionen
 admin.menu.title.alt=Lektionen und Absenzmanagement
 all=Alle
 appeal=Rekurs
-appeal.title=Rekurs "{0}"
-appeal.subject=Rekurs Lektionenblock "{0}"
 appeal.contact.list=Lehrer
+appeal.subject=Rekurs Lektionenblock "{0}"
+appeal.title=Rekurs "{0}"
+cancelled=Abgesagt
+config.calculate.attendance.rate=Presenz quota berechnen
+config.override=Standard Konfiguration \u00FCberschreiben
+config.override.no=Nein
+config.override.yes=Ja
+config.rollcall.enabled=Roll call einschalten
+config.sync.participant.calendar=Teilnehmer Kalender synchronizieren
+config.sync.teacher.calendar=Lehrer Kalender synchronizieren
+done=Erledigt
+edit.participant.rate=Teilnehmersschwellwert bearbeiten
 edit.reason=Begr\u00FCndung bearbeiten
+entry.rate=Kursschwellwert
 error.integer.between=Der Eingabe muss ein Zahl zwischen {0} und {1}
 error.reason.mandatory=Begr\u00FCndung ist erforderlich
 lecture.admin.enabled=Lektionen und absenzmanagement einschalten
 lecture.admin.title=Konfiguration lektionen und absenzmanagement
-menu.my.lectures=Absenzen
-menu.my.lectures.alt=Lektionen und Absenzen
-repo.settings=Konfiguration
-config.override=Standard Konfiguration \u00FCberschreiben
-config.override.yes=Ja
-config.override.no=Nein
-config.rollcall.enabled=Roll call einschalten
-config.calculate.attendance.rate=Presenz quota berechnen
-config.sync.teacher.calendar=Lehrer Kalender synchronizieren
-config.sync.participant.calendar=Teilnehmer Kalender synchronizieren
-repo.lectures=Lektionen
-lecture.title=Titel
+lecture.attendance.rate.default=Absenzenquote global in %
+lecture.authorized.absence.enabled=Entschuldigte Absenzen
+lecture.block.comment=Bemerkung
+lecture.block.effective.end=Effektives Ende
+lecture.block.effective.reason=Begr\u00FCndung
+lecture.block.status=Status
+lecture.date=Datum
 lecture.descr=Beschreibung
-lecture.preparation=Vorbereitung
+lecture.end=End
 lecture.groups=Studenten
 lecture.location=Ort
-lecture.date=Datum
+lecture.preparation=Vorbereitung
 lecture.start=Beginn
-lecture.end=End
 lecture.teacher=Lehrer
-lectures.admin.settings=Konfiguration
+lecture.title=Titel
 lectures.admin.reasons=Begr\u00FCndung
-lecture.attendance.rate.default=Absenzenquote global in %
-lecture.authorized.absence.enabled=Entschuldigte Absenzen
+lectures.admin.settings=Konfiguration
+menu.my.lectures=Absenzen
+menu.my.lectures.alt=Lektionen und Absenzen
+partiallydone=Teilweise erledigt 
+participant.rate=Schwellwert
 planned.lectures=Geplante Lektionen
 reason=Begr\u00FCndung
+reason.description=Beschreibung
 reason.id=ID
 reason.title=Bezeichnung
-reason.description=Beschreibung
+remove.custom.rate=Pers\u00F6nliches Schwellwert entfernen
+repo.lectures=Lektionen
+repo.lectures.block=Lektionenbl\u00F6cke
+repo.participants=Teilnehmer
+repo.settings=Konfiguration
+table.header.absence.reason=L
+table.header.authorized.absence=Entschuldigt
+table.header.coach=Dozent
+table.header.comment=Kommentar
+table.header.date=Datum
+table.header.details=Details
+table.header.end.time=Bis
+table.header.entry=Kurs
+table.header.export=Export
 table.header.lecture.1=1
+table.header.lecture.10=10
+table.header.lecture.11=11
+table.header.lecture.12=12
+table.header.lecture.13=13
+table.header.lecture.14=14
 table.header.lecture.2=2
 table.header.lecture.3=3
 table.header.lecture.4=4
@@ -51,24 +79,10 @@ table.header.lecture.6=6
 table.header.lecture.7=7
 table.header.lecture.8=8
 table.header.lecture.9=9
-table.header.lecture.10=10
-table.header.lecture.11=11
-table.header.lecture.12=12
-table.header.lecture.13=13
-table.header.lecture.14=14
-table.header.comment=Kommentar
-table.header.username=Benutzername
-table.header.authorized.absence=Entschuldigt
-table.header.absence.reason=L
-table.header.entry=Kurs
-table.header.quota=Quota
-table.header.progress=Progress
-table.header.date=Datum
 table.header.lecture.block=Lektionenblock
-table.header.coach=Dozent
 table.header.present=Anwesend
+table.header.progress=Progress
+table.header.quota=Quota
 table.header.start.time=Von
-table.header.end.time=Bis
 table.header.status=Status
-table.header.details=Details
-table.header.export=Export
\ No newline at end of file
+table.header.username=Benutzername
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 6956aca0a5e..02371d0cabf 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
@@ -1,47 +1,75 @@
-#Thu Sep 03 11:09:03 CEST 2015
+#Thu Apr 06 19:04:46 CEST 2017
+active=Active
 add.lecture=New lecture
 add.reason=Add reason
 admin.menu.title=Lectures
 admin.menu.title.alt=Lectures and absence management
 all=All
 appeal=Appeal
-appeal.title=Appeal "{0}"
-appeal.subject=Appeal lecture block "{0}"
 appeal.contact.list=Teacher
-edit.reason=Edit reason
-error.reason.mandatory=Reason is mandatory
-lecture.admin.enabled=Enable lectures and absence management
-lecture.admin.title=Configuration lectures and absence management
-menu.my.lectures=Absences
-menu.my.lectures.alt=Lectures and absences
-repo.lectures=Lectures
-repo.settings=Configuration
+appeal.subject=Appeal lecture block "{0}"
+appeal.title=Appeal "{0}"
+cancelled=Cancelled
+config.calculate.attendance.rate=Calculate attendance rate
 config.override=Override default configuration
-config.override.yes=Yes
 config.override.no=No
+config.override.yes=Yes
 config.rollcall.enabled=Roll call enabled
-config.calculate.attendance.rate=Calculate attendance rate
-config.sync.teacher.calendar=Synchronize teacher calendar
 config.sync.participant.calendar=Synchronize participants calendar
-lecture.title=Title
+config.sync.teacher.calendar=Synchronize teacher calendar
+done=Done
+edit.participant.rate=Edit participant's rate
+edit.reason=Edit reason
+entry.rate=Course's rate
+error.reason.mandatory=Reason is mandatory
+lecture.admin.enabled=Enable lectures and absence management
+lecture.admin.title=Configuration lectures and absence management
+lecture.attendance.rate.default=Absence quota global in %
+lecture.authorized.absence.enabled=Authorized absences
+lecture.block.comment=Comment
+lecture.block.effective.end=Effective end
+lecture.block.effective.reason=Reason
+lecture.block.status=Status
+lecture.date=Date
 lecture.descr=Description
-lecture.preparation=Preparation
+lecture.end=End
 lecture.groups=Students
 lecture.location=Location
-lecture.date=Date
+lecture.preparation=Preparation
 lecture.start=Begin
-lecture.end=End
 lecture.teacher=Teacher
-lectures.admin.settings=Settings
+lecture.title=Title
 lectures.admin.reasons=Reasons
-lecture.attendance.rate.default=Absence quota global in %
-lecture.authorized.absence.enabled=Authorized absences
+lectures.admin.settings=Settings
+menu.my.lectures=Absences
+menu.my.lectures.alt=Lectures and absences
+partiallydone=Partially done
+participant.rate=Rate
 planned.lectures=Planned lectures
 reason=Reason
+reason.description=Description
 reason.id=ID
 reason.title=Title
-reason.description=Description
+remove.custom.rate=Remove custom rate
+repo.lectures=Lectures
+repo.lectures.block=Lectures blocs
+repo.participants=Participants
+repo.settings=Configuration
+table.header.absence.reason=L
+table.header.authorized.absence=Excused
+table.header.coach=Coach
+table.header.comment=Comment
+table.header.date=Date
+table.header.details=Details
+table.header.end.time=To
+table.header.entry=Course
+table.header.export=Export
 table.header.lecture.1=1
+table.header.lecture.10=10
+table.header.lecture.11=11
+table.header.lecture.12=12
+table.header.lecture.13=13
+table.header.lecture.14=14
 table.header.lecture.2=2
 table.header.lecture.3=3
 table.header.lecture.4=4
@@ -50,24 +78,10 @@ table.header.lecture.6=6
 table.header.lecture.7=7
 table.header.lecture.8=8
 table.header.lecture.9=9
-table.header.lecture.10=10
-table.header.lecture.11=11
-table.header.lecture.12=12
-table.header.lecture.13=13
-table.header.lecture.14=14
-table.header.comment=Comment
-table.header.username=Username
-table.header.authorized.absence=Excused
-table.header.absence.reason=L
-table.header.entry=Course
-table.header.quota=Quota
-table.header.progress=Progress
-table.header.date=Date
 table.header.lecture.block=Lecture block
-table.header.coach=Coach
 table.header.present=Present
+table.header.progress=Progress
+table.header.quota=Quota
 table.header.start.time=From
-table.header.end.time=To
 table.header.status=Status
-table.header.details=Details
-table.header.export=Export
\ No newline at end of file
+table.header.username=Username
diff --git a/src/main/java/org/olat/modules/lecture/ui/component/LectureBlockStatusCellRenderer.java b/src/main/java/org/olat/modules/lecture/ui/component/LectureBlockStatusCellRenderer.java
new file mode 100644
index 00000000000..12284766a9b
--- /dev/null
+++ b/src/main/java/org/olat/modules/lecture/ui/component/LectureBlockStatusCellRenderer.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.LectureBlockStatus;
+
+/**
+ * 
+ * Initial date: 6 avr. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class LectureBlockStatusCellRenderer implements FlexiCellRenderer {
+	
+	private final Translator translator;
+	
+	public LectureBlockStatusCellRenderer(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 LectureBlockStatus) {
+			LectureBlockStatus status = (LectureBlockStatus)cellValue;
+			target.append(translator.translate(status.name()));
+		}
+	}
+}
diff --git a/src/main/java/org/olat/modules/lecture/ui/component/LectureStatisticsCellRenderer.java b/src/main/java/org/olat/modules/lecture/ui/component/LectureStatisticsCellRenderer.java
index 606b76e19ec..5ece306c060 100644
--- a/src/main/java/org/olat/modules/lecture/ui/component/LectureStatisticsCellRenderer.java
+++ b/src/main/java/org/olat/modules/lecture/ui/component/LectureStatisticsCellRenderer.java
@@ -26,6 +26,7 @@ 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.LectureStatistics;
+import org.olat.modules.lecture.model.ParticipantLectureStatistics;
 
 /**
  * 
@@ -35,30 +36,37 @@ public class LectureStatisticsCellRenderer implements FlexiCellRenderer {
 
 	@Override
 	public void render(Renderer renderer, StringOutput target, Object cellValue, int row, FlexiTableComponent source, URLBuilder ubu, Translator translator) {
-
 		if(cellValue instanceof LectureStatistics) {
 			LectureStatistics stats = (LectureStatistics)cellValue;
 			long total = stats.getTotalPlannedLectures();
 			long attended = stats.getTotalAttendedLectures();
-			long attendedPercent = (attended == 0) ? 0 : Math.round(100.0f * ((double)attended / (double)total));
 			long absent = stats.getTotalAbsentLectures();
-			long absentPercent = (absent == 0) ? 0 :  Math.round(100.0f * ((double)absent / (double)total));
-			
-			target.append("<div class='progress'>");
-			//attended
-			target.append("<div class='progress-bar o_lecture_attended' role='progressbar' aria-valuenow='").append(attended)
-			      .append("' aria-valuemin='0' aria-valuemax='").append(total)
-			      .append("' style='width: ").append(attendedPercent).append("%;'>")
-			      .append("<span class='sr-only'>").append(attendedPercent).append("%</span></div>");
-			//absent
-			target.append("<div class='progress-bar o_lecture_absent' role='progressbar' aria-valuenow='").append(absent)
-		      .append("' aria-valuemin='0' aria-valuemax='").append(total)
-		      .append("' style='width: ").append(absentPercent).append("%;'>")
-		      .append("<span class='sr-only'>").append(absentPercent).append("%</span></div>");
-			
-			
-			target.append("</div>");
-			
+			render(target, total, attended, absent);
+		} else if(cellValue instanceof ParticipantLectureStatistics) {
+			ParticipantLectureStatistics stats = (ParticipantLectureStatistics)cellValue;
+			long total = stats.getTotalPlannedLectures();
+			long attended = stats.getTotalAttendedLectures();
+			long absent = stats.getTotalAbsentLectures();
+			render(target, total, attended, absent);
 		}
 	}
+	
+	private void render(StringOutput target, long total, long attended, long absent) {
+		long attendedPercent = (attended == 0) ? 0 : Math.round(100.0f * ((double)attended / (double)total));
+		long absentPercent = (absent == 0) ? 0 :  Math.round(100.0f * ((double)absent / (double)total));
+		target.append("<div class='progress'>");
+		//attended
+		target.append("<div class='progress-bar o_lecture_attended' role='progressbar' aria-valuenow='").append(attended)
+		      .append("' aria-valuemin='0' aria-valuemax='").append(total)
+		      .append("' style='width: ").append(attendedPercent).append("%;'>")
+		      .append("<span class='sr-only'>").append(attendedPercent).append("%</span></div>");
+		//absent
+		target.append("<div class='progress-bar o_lecture_absent' role='progressbar' aria-valuenow='").append(absent)
+	      .append("' aria-valuemin='0' aria-valuemax='").append(total)
+	      .append("' style='width: ").append(absentPercent).append("%;'>")
+	      .append("<span class='sr-only'>").append(absentPercent).append("%</span></div>");
+		
+		
+		target.append("</div>");
+	}
 }
diff --git a/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml b/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml
index f678b0bce6d..b21b2155678 100644
--- a/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml
+++ b/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml
@@ -358,6 +358,29 @@
 					</bean>
 				</entry>
 				
+				<entry key="org.olat.modules.lecture.ui.ParticipantListRepositoryController">
+					<bean class="org.olat.user.propertyhandlers.UserPropertyUsageContext">
+						<property name="description" value="List of users in lecture blocks overview" />
+						<property name="propertyHandlers">
+							<list>
+								<ref bean="userPropertyFirstName" />
+								<ref bean="userPropertyLastName" />
+								<ref bean="userPropertyEmail" />
+							</list>
+						</property>
+						<property name="adminViewOnlyProperties">
+							<set></set>						
+						</property>
+						<property name="mandatoryProperties">
+							<set>
+								<ref bean="userPropertyFirstName" />
+								<ref bean="userPropertyLastName" />
+								<ref bean="userPropertyEmail" />
+							</set>
+						</property>
+					</bean>
+				</entry>
+				
 				<entry key="org.olat.group.ui.main.MemberInfoController">
 					<!-- First name and last name are already shown -->
 					<bean class="org.olat.user.propertyhandlers.UserPropertyUsageContext">
diff --git a/src/main/resources/database/mysql/alter_11_4_x_to_11_5_0.sql b/src/main/resources/database/mysql/alter_11_4_x_to_11_5_0.sql
index dfb6ce86017..37b4ba3daba 100644
--- a/src/main/resources/database/mysql/alter_11_4_x_to_11_5_0.sql
+++ b/src/main/resources/database/mysql/alter_11_4_x_to_11_5_0.sql
@@ -79,7 +79,7 @@ create table o_lecture_participant_summary (
   id bigint not null auto_increment,
   creationdate datetime not null,
   lastmodified datetime not null,
-  l_quota float(65,30) default null,
+  l_attendance_rate float(65,30) default null,
   l_first_admission_date datetime default null,
   l_attended_lectures bigint not null default 0,
   l_absent_lectures bigint not null default 0,
diff --git a/src/main/resources/database/mysql/setupDatabase.sql b/src/main/resources/database/mysql/setupDatabase.sql
index ec353d4e2f6..7071c2c96dd 100644
--- a/src/main/resources/database/mysql/setupDatabase.sql
+++ b/src/main/resources/database/mysql/setupDatabase.sql
@@ -1919,6 +1919,99 @@ create table o_sms_message_log (
    primary key (id)
 );
 
+-- lectures
+create table o_lecture_reason (
+  id bigint not null auto_increment,
+  creationdate datetime not null,
+  lastmodified datetime not null,
+  l_title varchar(255),
+  l_descr varchar(2000),
+  primary key (id)
+);
+
+create table o_lecture_block (
+  id bigint not null auto_increment,
+  creationdate datetime not null,
+  lastmodified datetime not null,
+  l_external_id varchar(255),
+  l_title varchar(255),
+  l_descr mediumtext,
+  l_preparation mediumtext,
+  l_location varchar(255),
+  l_comment mediumtext, 
+  l_log mediumtext,
+  l_start_date datetime not null,
+  l_end_date datetime not null,
+  l_eff_end_date datetime,
+  l_planned_lectures_num bigint not null default 0,
+  l_effective_lectures_num bigint not null default 0,
+  l_effective_lectures varchar(128),
+  l_status varchar(16) not null,
+  l_roll_call_status varchar(16) not null,
+  fk_reason bigint,
+  fk_entry bigint not null,
+  fk_teacher_group bigint not null,
+  primary key (id)
+);
+
+create table o_lecture_block_to_group (
+  id bigint not null auto_increment,
+  fk_lecture_block bigint not null,
+  fk_group bigint not null,
+  primary key (id)
+);
+
+create table o_lecture_block_roll_call (
+  id bigint not null auto_increment,
+  creationdate datetime not null,
+  lastmodified datetime not null,
+  l_comment mediumtext, 
+  l_log mediumtext,
+  l_lectures_attended varchar(128),
+  l_lectures_absent varchar(128),
+  l_lectures_attended_num bigint not null default 0,
+  l_lectures_absent_num bigint not null default 0,
+  l_absence_reason mediumtext,
+  l_absence_authorized bit default null,
+  l_absence_appeal_date datetime,
+  fk_lecture_block bigint not null,
+  fk_identity bigint not null,
+  primary key (id)
+);
+
+create table o_lecture_participant_summary (
+  id bigint not null auto_increment,
+  creationdate datetime not null,
+  lastmodified datetime not null,
+  l_attendance_rate float(65,30) default null,
+  l_first_admission_date datetime default null,
+  l_attended_lectures bigint not null default 0,
+  l_absent_lectures bigint not null default 0,
+  l_excused_lectures bigint not null default 0,
+  l_planneds_lectures bigint not null default 0, 
+  fk_entry bigint not null,
+  fk_identity bigint not null,
+  primary key (id),
+  unique (fk_entry, fk_identity)
+);
+
+create table o_lecture_entry_config (
+  id bigint not null auto_increment,
+  creationdate datetime not null,
+  lastmodified datetime not null,
+  l_lecture_enabled bool default null,
+  l_override_module_def bool default false,
+  l_rollcall_enabled bool default null,
+  l_calculate_attendance_rate bool default null,
+  l_required_attendance_rate float(65,30) default null,
+  l_sync_calendar_teacher bool default null,
+  l_sync_calendar_participant bool default null,
+  fk_entry bigint not null,
+  unique(fk_entry),
+  primary key (id)
+);
+
+
 -- user view
 create view o_bs_identity_short_v as (
    select
@@ -2250,6 +2343,12 @@ alter table o_pf_binder_user_infos ENGINE = InnoDB;
 alter table o_eva_form_session ENGINE = InnoDB;
 alter table o_eva_form_response ENGINE = InnoDB;
 alter table o_sms_message_log ENGINE = InnoDB;
+alter table o_lecture_reason ENGINE = InnoDB;
+alter table o_lecture_block ENGINE = InnoDB;
+alter table o_lecture_block_to_group ENGINE = InnoDB;
+alter table o_lecture_block_roll_call ENGINE = InnoDB;
+alter table o_lecture_participant_summary ENGINE = InnoDB;
+alter table o_lecture_entry_config ENGINE = InnoDB;
 
 -- rating
 alter table o_userrating add constraint FKF26C8375236F20X foreign key (creator_id) references o_bs_identity (id);
@@ -2709,6 +2808,22 @@ create index cer_uuid_idx on o_cer_certificate (c_uuid);
 -- sms
 alter table o_sms_message_log add constraint sms_log_to_identity_idx foreign key (fk_identity) references o_bs_identity (id);
 
+-- lecture
+alter table o_lecture_block add constraint lec_block_entry_idx foreign key (fk_entry) references o_repositoryentry (repositoryentry_id);
+alter table o_lecture_block add constraint lec_block_gcoach_idx foreign key (fk_teacher_group) references o_bs_group (id);
+alter table o_lecture_block add constraint lec_block_reason_idx foreign key (fk_reason) references o_lecture_reason (id);
+
+alter table o_lecture_block_to_group add constraint lec_block_to_block_idx foreign key (fk_group) references o_bs_group (id);
+alter table o_lecture_block_to_group add constraint lec_block_to_group_idx foreign key (fk_lecture_block) references o_lecture_block (id);
+
+alter table o_lecture_block_roll_call add constraint lec_call_block_idx foreign key (fk_lecture_block) references o_lecture_block (id);
+alter table o_lecture_block_roll_call add constraint lec_call_identity_idx foreign key (fk_identity) references o_bs_identity (id);
+
+alter table o_lecture_participant_summary add constraint lec_part_entry_idx foreign key (fk_entry) references o_repositoryentry (repositoryentry_id);
+alter table o_lecture_participant_summary add constraint lec_part_ident_idx foreign key (fk_identity) references o_bs_identity (id);
+
+alter table o_lecture_entry_config add constraint lec_entry_config_entry_idx foreign key (fk_entry) references o_repositoryentry (repositoryentry_id);
+
 -- o_logging_table
 create index log_target_resid_idx on o_loggingtable(targetresid);
 create index log_ptarget_resid_idx on o_loggingtable(parentresid);
diff --git a/src/main/resources/database/postgresql/alter_11_4_x_to_11_5_0.sql b/src/main/resources/database/postgresql/alter_11_4_x_to_11_5_0.sql
index 186bd0814d6..697f8f1b329 100644
--- a/src/main/resources/database/postgresql/alter_11_4_x_to_11_5_0.sql
+++ b/src/main/resources/database/postgresql/alter_11_4_x_to_11_5_0.sql
@@ -82,7 +82,7 @@ create table o_lecture_participant_summary (
   id bigserial not null,
   creationdate timestamp not null,
   lastmodified timestamp not null,
-  l_quota float(24) default null,
+  l_attendance_rate float(24) default null,
   l_first_admission_date timestamp default null,
   l_attended_lectures int8 not null default 0,
   l_absent_lectures int8 not null default 0,
diff --git a/src/main/resources/database/postgresql/setupDatabase.sql b/src/main/resources/database/postgresql/setupDatabase.sql
index 0910aee819b..59232fdbbcc 100644
--- a/src/main/resources/database/postgresql/setupDatabase.sql
+++ b/src/main/resources/database/postgresql/setupDatabase.sql
@@ -1916,6 +1916,99 @@ create table o_sms_message_log (
    primary key (id)
 );
 
+-- lectures
+create table o_lecture_reason (
+  id bigserial not null,
+  creationdate timestamp not null,
+  lastmodified timestamp not null,
+  l_title varchar(255),
+  l_descr varchar(2000),
+  primary key (id)
+);
+
+
+create table o_lecture_block (
+  id bigserial not null,
+  creationdate timestamp not null,
+  lastmodified timestamp not null,
+  l_external_id varchar(255),
+  l_title varchar(255),
+  l_descr text,
+  l_preparation text,
+  l_location varchar(255),
+  l_comment text, 
+  l_log text,
+  l_start_date timestamp not null,
+  l_end_date timestamp not null,
+  l_eff_end_date timestamp,
+  l_planned_lectures_num int8 not null default 0,
+  l_effective_lectures_num int8 not null default 0,
+  l_effective_lectures varchar(128),
+  l_status varchar(16) not null,
+  l_roll_call_status varchar(16) not null,
+  fk_reason int8,
+  fk_entry int8 not null,
+  fk_teacher_group int8 not null,
+  primary key (id)
+);
+
+create table o_lecture_block_to_group (
+  id bigserial not null,
+  fk_lecture_block int8 not null,
+  fk_group int8 not null,
+  primary key (id)
+);
+
+create table o_lecture_block_roll_call (
+  id bigserial not null,
+  creationdate timestamp not null,
+  lastmodified timestamp not null,
+  l_comment text, 
+  l_log text,
+  l_lectures_attended varchar(128),
+  l_lectures_absent varchar(128),
+  l_lectures_attended_num int8 not null default 0,
+  l_lectures_absent_num int8 not null default 0,
+  l_absence_reason text,
+  l_absence_authorized bool default null,
+  l_absence_appeal_date timestamp,
+  fk_lecture_block int8 not null,
+  fk_identity int8 not null,
+  primary key (id)
+);
+
+create table o_lecture_participant_summary (
+  id bigserial not null,
+  creationdate timestamp not null,
+  lastmodified timestamp not null,
+  l_attendance_rate float(24) default null,
+  l_first_admission_date timestamp default null,
+  l_attended_lectures int8 not null default 0,
+  l_absent_lectures int8 not null default 0,
+  l_excused_lectures int8 not null default 0,
+  l_planneds_lectures int8 not null default 0, 
+  fk_entry int8 not null,
+  fk_identity int8 not null,
+  primary key (id),
+  unique (fk_entry, fk_identity)
+);
+
+create table o_lecture_entry_config (
+  id bigserial not null,
+  creationdate timestamp not null,
+  lastmodified timestamp not null,
+  l_lecture_enabled bool default null,
+  l_override_module_def bool default false,
+  l_rollcall_enabled bool default null,
+  l_calculate_attendance_rate bool default null,
+  l_required_attendance_rate float(24) default null,
+  l_sync_calendar_teacher bool default null,
+  l_sync_calendar_participant bool default null,
+  fk_entry int8 not null,
+  unique(fk_entry),
+  primary key (id)
+);
+
 -- user view
 create view o_bs_identity_short_v as (
    select
@@ -2753,6 +2846,32 @@ create index cer_uuid_idx on o_cer_certificate (c_uuid);
 alter table o_sms_message_log add constraint sms_log_to_identity_idx foreign key (fk_identity) references o_bs_identity (id);
 create index idx_sms_log_to_identity_idx on o_sms_message_log(fk_identity);
 
+-- lectures
+alter table o_lecture_block add constraint lec_block_entry_idx foreign key (fk_entry) references o_repositoryentry (repositoryentry_id);
+create index idx_lec_block_entry_idx on o_lecture_block(fk_entry);
+alter table o_lecture_block add constraint lec_block_gcoach_idx foreign key (fk_teacher_group) references o_bs_group (id);
+create index idx_lec_block_gcoach_idx on o_lecture_block(fk_teacher_group);
+alter table o_lecture_block add constraint lec_block_reason_idx foreign key (fk_reason) references o_lecture_reason (id);
+create index idx_lec_block_reason_idx on o_lecture_block(fk_reason);
+
+alter table o_lecture_block_to_group add constraint lec_block_to_block_idx foreign key (fk_group) references o_bs_group (id);
+create index idx_lec_block_to_block_idx on o_lecture_block_to_group(fk_group);
+alter table o_lecture_block_to_group add constraint lec_block_to_group_idx foreign key (fk_lecture_block) references o_lecture_block (id);
+create index idx_lec_block_to_group_idx on o_lecture_block_to_group(fk_lecture_block);
+
+alter table o_lecture_block_roll_call add constraint lec_call_block_idx foreign key (fk_lecture_block) references o_lecture_block (id);
+create index idx_lec_call_block_idx on o_lecture_block_roll_call(fk_lecture_block);
+alter table o_lecture_block_roll_call add constraint lec_call_identity_idx foreign key (fk_identity) references o_bs_identity (id);
+create index idx_lec_call_identity_idx on o_lecture_block_roll_call(fk_identity);
+
+alter table o_lecture_participant_summary add constraint lec_part_entry_idx foreign key (fk_entry) references o_repositoryentry (repositoryentry_id);
+create index idx_lec_part_entry_idx on o_lecture_participant_summary(fk_entry);
+alter table o_lecture_participant_summary add constraint lec_part_ident_idx foreign key (fk_identity) references o_bs_identity (id);
+create index idx_lec_part_ident_idx on o_lecture_participant_summary(fk_identity);
+
+alter table o_lecture_entry_config add constraint lec_entry_config_entry_idx foreign key (fk_entry) references o_repositoryentry (repositoryentry_id);
+create index idx_lec_entry_conf_entry_idx on o_lecture_entry_config(fk_entry);
+
 -- o_logging_table
 create index log_target_resid_idx on o_loggingtable(targetresid);
 create index log_ptarget_resid_idx on o_loggingtable(parentresid);
-- 
GitLab