From daeff2775d1dd4f11c2dcf03cae7d0b2ae4b3de3 Mon Sep 17 00:00:00 2001 From: uhensler <urs.hensler@frentix.com> Date: Tue, 24 Mar 2020 08:57:28 +0100 Subject: [PATCH] OO-4544: Announcement of a data collection --- .../java/org/olat/core/util/DateUtils.java | 8 ++ .../modules/quality/QualityReminderType.java | 2 + .../olat/modules/quality/QualityService.java | 2 + .../CourseLecturesFollowUpProvider.java | 26 +++++- .../CourseLecturesProvider.java | 21 ++++- ...ctureFollowUpProviderConfigController.java | 10 ++ ...CourseLectureProviderConfigController.java | 10 ++ .../ui/_i18n/LocalStrings_de.properties | 1 + .../ui/_i18n/LocalStrings_en.properties | 3 +- .../quality/manager/QualityContextDAO.java | 19 +++- .../quality/manager/QualityMailing.java | 84 ++++++++++++++--- .../quality/manager/QualityReminderDAO.java | 30 +++++- .../quality/manager/QualityServiceImpl.java | 32 ++++++- .../quality/ui/RemindersController.java | 39 +++++++- .../ui/_i18n/LocalStrings_de.properties | 6 ++ .../ui/_i18n/LocalStrings_en.properties | 6 ++ .../mysql/alter_15_pre_6_to_15_pre_7.sql | 3 + .../database/mysql/setupDatabase.sql | 2 +- .../oracle/alter_15_pre_6_to_15_pre_7.sql | 3 + .../database/oracle/setupDatabase.sql | 2 +- .../postgresql/alter_15_pre_6_to_15_pre_7.sql | 3 + .../database/postgresql/setupDatabase.sql | 2 +- .../CourseLecturesProviderTest.java | 60 ++++++++++++ .../manager/QualityContextDAOTest.java | 21 +++++ .../manager/QualityReminderDAOTest.java | 92 +++++++++++++++---- 25 files changed, 443 insertions(+), 44 deletions(-) diff --git a/src/main/java/org/olat/core/util/DateUtils.java b/src/main/java/org/olat/core/util/DateUtils.java index 33f7a910d26..269e2799641 100644 --- a/src/main/java/org/olat/core/util/DateUtils.java +++ b/src/main/java/org/olat/core/util/DateUtils.java @@ -23,6 +23,7 @@ import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; +import java.util.Calendar; import java.util.Date; /** @@ -57,6 +58,13 @@ public class DateUtils { return Instant.ofEpochMilli(date.getTime()).atZone(ZoneId.systemDefault()).toLocalDateTime(); } + public static Date addDays(Date date, int days) { + Calendar c = Calendar.getInstance(); + c.setTime(date); + c.add(Calendar.DATE, days); + return c.getTime(); + } + public static Date getLater(Date date1, Date date2) { if (date1 == null) return date2; if (date2 == null) return date1; diff --git a/src/main/java/org/olat/modules/quality/QualityReminderType.java b/src/main/java/org/olat/modules/quality/QualityReminderType.java index 03c3f2682cf..ec4fd068812 100644 --- a/src/main/java/org/olat/modules/quality/QualityReminderType.java +++ b/src/main/java/org/olat/modules/quality/QualityReminderType.java @@ -29,6 +29,8 @@ import org.olat.modules.forms.EvaluationFormParticipationStatus; */ public enum QualityReminderType { + ANNOUNCEMENT_COACH_TOPIC("reminder.announcement.coach.topic.subject", "reminder.announcement.coach.topic.body", null), + ANNOUNCEMENT_COACH_CONTEXT("reminder.announcement.coach.context.subject", "reminder.announcement.coach.context.body", null), INVITATION("reminder.invitation.subject", "reminder.invitation.body", null), REMINDER1("reminder.reminder1.subject", "reminder.reminder1.body", EvaluationFormParticipationStatus.prepared), REMINDER2("reminder.reminder2.subject", "reminder.reminder2.body", EvaluationFormParticipationStatus.prepared); diff --git a/src/main/java/org/olat/modules/quality/QualityService.java b/src/main/java/org/olat/modules/quality/QualityService.java index 111fede8cd1..ce8c46a97a1 100644 --- a/src/main/java/org/olat/modules/quality/QualityService.java +++ b/src/main/java/org/olat/modules/quality/QualityService.java @@ -190,6 +190,8 @@ public interface QualityService { public QualityReminder updateReminderDatePlaned(QualityReminder invitation, Date datePlaned); + public List<QualityReminder> loadReminders(QualityDataCollectionRef dataCollectionRef); + public QualityReminder loadReminder(QualityDataCollectionRef dataCollectionRef, QualityReminderType type); public void deleteReminder(QualityReminder reminder); diff --git a/src/main/java/org/olat/modules/quality/generator/provider/courselectures/CourseLecturesFollowUpProvider.java b/src/main/java/org/olat/modules/quality/generator/provider/courselectures/CourseLecturesFollowUpProvider.java index 6abe61e0e78..0f5a3370776 100644 --- a/src/main/java/org/olat/modules/quality/generator/provider/courselectures/CourseLecturesFollowUpProvider.java +++ b/src/main/java/org/olat/modules/quality/generator/provider/courselectures/CourseLecturesFollowUpProvider.java @@ -21,6 +21,7 @@ package org.olat.modules.quality.generator.provider.courselectures; import static org.olat.modules.quality.generator.ProviderHelper.addDays; import static org.olat.modules.quality.generator.ProviderHelper.addMinutes; +import static org.olat.modules.quality.generator.ProviderHelper.subtractDays; import static org.olat.modules.quality.generator.ProviderHelper.toDouble; import java.util.ArrayList; @@ -59,7 +60,6 @@ import org.olat.modules.quality.QualityDataCollectionStatus; import org.olat.modules.quality.QualityDataCollectionTopicType; import org.olat.modules.quality.QualityReminderType; import org.olat.modules.quality.QualityService; -import org.olat.modules.quality.generator.ProviderHelper; import org.olat.modules.quality.generator.QualityGenerator; import org.olat.modules.quality.generator.QualityGeneratorConfigs; import org.olat.modules.quality.generator.QualityGeneratorProvider; @@ -93,6 +93,7 @@ public class CourseLecturesFollowUpProvider implements QualityGeneratorProvider public static final String CONFIG_KEY_GRADE_TOTAL_CHECK_KEY = "grade.total.check.key"; public static final String CONFIG_KEY_GRADE_SINGLE_LIMIT = "grade.single.limit"; public static final String CONFIG_KEY_GRADE_SINGLE_CHECK_KEY = "grade.single.check.key"; + public static final String CONFIG_KEY_ANNOUNCEMENT_COACH_DAYS = "accouncement.coach.days"; public static final String CONFIG_KEY_INVITATION_AFTER_DC_START_DAYS = "invitation.after.dc.start.days"; public static final String CONFIG_KEY_MINUTES_BEFORE_END = "minutes before end"; public static final String CONFIG_KEY_PREVIOUS_GENERATOR_KEY = "previous.generator.key"; @@ -220,12 +221,15 @@ public class CourseLecturesFollowUpProvider implements QualityGeneratorProvider String title = titleCreator.merge(titleTemplate, Arrays.asList(course, teacher.getUser())); dataCollection.setTitle(title); + QualityReminderType coachReminderType = null; if (CourseLecturesProvider.CONFIG_KEY_TOPIC_COACH.equals(topicKey)) { dataCollection.setTopicType(QualityDataCollectionTopicType.IDENTIY); dataCollection.setTopicIdentity(teacher); + coachReminderType = QualityReminderType.ANNOUNCEMENT_COACH_TOPIC; } else if (CourseLecturesProvider.CONFIG_KEY_TOPIC_COURSE.equals(topicKey)) { dataCollection.setTopicType(QualityDataCollectionTopicType.REPOSITORY); dataCollection.setTopicRepositoryEntry(course); + coachReminderType = QualityReminderType.ANNOUNCEMENT_COACH_CONTEXT; } dataCollection = qualityService.updateDataCollectionStatus(dataCollection, QualityDataCollectionStatus.READY); @@ -258,7 +262,15 @@ public class CourseLecturesFollowUpProvider implements QualityGeneratorProvider } } - // make reminders + // make reminder + String announcementDay = configs.getValue(CONFIG_KEY_ANNOUNCEMENT_COACH_DAYS); + if (StringHelper.containsNonWhitespace(announcementDay) && coachReminderType != null) { + Date announcementDate = subtractDays(dcStart, announcementDay); + if (dataCollection.getStart().after(new Date())) { // no announcement if already started + qualityService.createReminder(dataCollection, announcementDate, coachReminderType); + } + } + String invitationDay = configs.getValue(CONFIG_KEY_INVITATION_AFTER_DC_START_DAYS); if (StringHelper.containsNonWhitespace(invitationDay)) { Date invitationDate = addDays(dcStart, invitationDay); @@ -302,9 +314,15 @@ public class CourseLecturesFollowUpProvider implements QualityGeneratorProvider String minutesBeforeEnd = configs.getValue(CONFIG_KEY_MINUTES_BEFORE_END); minutesBeforeEnd = StringHelper.containsNonWhitespace(minutesBeforeEnd)? minutesBeforeEnd: "0"; - Date from = ProviderHelper.addMinutes(fromDate, minutesBeforeEnd); + Date from = addMinutes(fromDate, minutesBeforeEnd); + Date to = addMinutes(toDate, minutesBeforeEnd); + + String announcementDays = configs.getValue(CONFIG_KEY_ANNOUNCEMENT_COACH_DAYS); + if (StringHelper.containsNonWhitespace(announcementDays)) { + to = addDays(to, announcementDays); + } + searchParams.setFrom(from); - Date to = ProviderHelper.addMinutes(toDate, minutesBeforeEnd); searchParams.setTo(to); searchParams.setLastLectureBlock(true); diff --git a/src/main/java/org/olat/modules/quality/generator/provider/courselectures/CourseLecturesProvider.java b/src/main/java/org/olat/modules/quality/generator/provider/courselectures/CourseLecturesProvider.java index 576b7724e45..787136f4491 100644 --- a/src/main/java/org/olat/modules/quality/generator/provider/courselectures/CourseLecturesProvider.java +++ b/src/main/java/org/olat/modules/quality/generator/provider/courselectures/CourseLecturesProvider.java @@ -21,6 +21,7 @@ package org.olat.modules.quality.generator.provider.courselectures; import static org.olat.modules.quality.generator.ProviderHelper.addDays; import static org.olat.modules.quality.generator.ProviderHelper.addMinutes; +import static org.olat.modules.quality.generator.ProviderHelper.subtractDays; import java.util.ArrayList; import java.util.Arrays; @@ -83,6 +84,7 @@ public class CourseLecturesProvider implements QualityGeneratorProvider { public static final String TYPE = "course-lecture"; public static final String CONFIG_KEY_DURATION_DAYS = "duration.days"; + public static final String CONFIG_KEY_ANNOUNCEMENT_COACH_DAYS = "announcement.coach.days"; public static final String CONFIG_KEY_INVITATION_AFTER_DC_START_DAYS = "invitation.after.dc.start.days"; public static final String CONFIG_KEY_MINUTES_BEFORE_END = "minutes before end"; public static final String CONFIG_KEY_REMINDER1_AFTER_DC_DAYS = "reminder1.after.dc.start.days"; @@ -214,12 +216,15 @@ public class CourseLecturesProvider implements QualityGeneratorProvider { String title = titleCreator.merge(titleTemplate, Arrays.asList(course, teacher.getUser())); dataCollection.setTitle(title); + QualityReminderType coachReminderType = null; if (CONFIG_KEY_TOPIC_COACH.equals(topicKey)) { dataCollection.setTopicType(QualityDataCollectionTopicType.IDENTIY); dataCollection.setTopicIdentity(teacher); + coachReminderType = QualityReminderType.ANNOUNCEMENT_COACH_TOPIC; } else if (CONFIG_KEY_TOPIC_COURSE.equals(topicKey)) { dataCollection.setTopicType(QualityDataCollectionTopicType.REPOSITORY); dataCollection.setTopicRepositoryEntry(course); + coachReminderType = QualityReminderType.ANNOUNCEMENT_COACH_CONTEXT; } dataCollection = qualityService.updateDataCollectionStatus(dataCollection, QualityDataCollectionStatus.READY); @@ -253,6 +258,14 @@ public class CourseLecturesProvider implements QualityGeneratorProvider { } // make reminders + String announcementDay = configs.getValue(CONFIG_KEY_ANNOUNCEMENT_COACH_DAYS); + if (StringHelper.containsNonWhitespace(announcementDay) && coachReminderType != null) { + Date announcementDate = subtractDays(dcStart, announcementDay); + if (dataCollection.getStart().after(new Date())) { // no announcement if already started + qualityService.createReminder(dataCollection, announcementDate, coachReminderType); + } + } + String invitationDay = configs.getValue(CONFIG_KEY_INVITATION_AFTER_DC_START_DAYS); if (StringHelper.containsNonWhitespace(invitationDay)) { Date invitationDate = addDays(dcStart, invitationDay); @@ -287,8 +300,14 @@ public class CourseLecturesProvider implements QualityGeneratorProvider { String minutesBeforeEnd = configs.getValue(CONFIG_KEY_MINUTES_BEFORE_END); minutesBeforeEnd = StringHelper.containsNonWhitespace(minutesBeforeEnd)? minutesBeforeEnd: "0"; Date from = addMinutes(fromDate, minutesBeforeEnd); - searchParams.setFrom(from); Date to = addMinutes(toDate, minutesBeforeEnd); + + String announcementDays = configs.getValue(CONFIG_KEY_ANNOUNCEMENT_COACH_DAYS); + if (StringHelper.containsNonWhitespace(announcementDays)) { + to = addDays(to, announcementDays); + } + + searchParams.setFrom(from); searchParams.setTo(to); Collection<CurriculumElementRef> curriculumElementRefs = CurriculumElementWhiteListController.getCurriculumElementRefs(configs); diff --git a/src/main/java/org/olat/modules/quality/generator/provider/courselectures/ui/CourseLectureFollowUpProviderConfigController.java b/src/main/java/org/olat/modules/quality/generator/provider/courselectures/ui/CourseLectureFollowUpProviderConfigController.java index b0f4d1cef13..98e354811e5 100644 --- a/src/main/java/org/olat/modules/quality/generator/provider/courselectures/ui/CourseLectureFollowUpProviderConfigController.java +++ b/src/main/java/org/olat/modules/quality/generator/provider/courselectures/ui/CourseLectureFollowUpProviderConfigController.java @@ -65,6 +65,7 @@ public class CourseLectureFollowUpProviderConfigController extends ProviderConfi private TextElement lecturesTotalMinEl; private TextElement durationEl; private TextElement minutesBeforeEndEl; + private TextElement announcementCoachDaysEl; private TextElement invitationDaysEl; private TextElement reminder1DaysEl; private TextElement reminder2DaysEl; @@ -140,6 +141,9 @@ public class CourseLectureFollowUpProviderConfigController extends ProviderConfi durationEl = uifactory.addTextElement("config.duration", 4, duration, formLayout); // reminders + String announcementCoachDays = configs.getValue(CourseLecturesFollowUpProvider.CONFIG_KEY_ANNOUNCEMENT_COACH_DAYS); + announcementCoachDaysEl = uifactory.addTextElement("config.announcement.coach.days", 4, announcementCoachDays, formLayout); + String invitationDays = configs.getValue(CourseLecturesFollowUpProvider.CONFIG_KEY_INVITATION_AFTER_DC_START_DAYS); invitationDaysEl = uifactory.addTextElement("config.invitation.days", 4, invitationDays, formLayout); @@ -157,6 +161,7 @@ public class CourseLectureFollowUpProviderConfigController extends ProviderConfi previousGeneratorEl.setEnabled(enabled); lecturesTotalMinEl.setEnabled(enabled); minutesBeforeEndEl.setEnabled(enabled); + announcementCoachDaysEl.setEnabled(enabled); invitationDaysEl.setEnabled(enabled); reminder1DaysEl.setEnabled(enabled); reminder2DaysEl.setEnabled(enabled); @@ -175,6 +180,7 @@ public class CourseLectureFollowUpProviderConfigController extends ProviderConfi allOk &= validateInteger(lecturesTotalMinEl, 1, 10000); allOk &= validateIsMandatory(minutesBeforeEndEl) && validateInteger(minutesBeforeEndEl, 0, 1000); allOk &= validateIsMandatory(durationEl) && validateInteger(durationEl, 1, 10000); + allOk &= validateInteger(announcementCoachDaysEl, 0, 10000); allOk &= validateInteger(invitationDaysEl, 0, 10000); allOk &= validateInteger(reminder1DaysEl, 1, 10000); allOk &= validateInteger(reminder2DaysEl, 1, 10000); @@ -190,6 +196,7 @@ public class CourseLectureFollowUpProviderConfigController extends ProviderConfi lecturesTotalMinEl.clearError(); minutesBeforeEndEl.clearError(); durationEl.clearError(); + announcementCoachDaysEl.clearError(); invitationDaysEl.clearError(); reminder1DaysEl.clearError(); reminder2DaysEl.clearError(); @@ -226,6 +233,9 @@ public class CourseLectureFollowUpProviderConfigController extends ProviderConfi String duration = durationEl.getValue(); configs.setValue(CourseLecturesFollowUpProvider.CONFIG_KEY_DURATION_DAYS, duration); + String announcementCoachDays = announcementCoachDaysEl.getValue(); + configs.setValue(CourseLecturesFollowUpProvider.CONFIG_KEY_ANNOUNCEMENT_COACH_DAYS, announcementCoachDays); + String invitationDays = invitationDaysEl.getValue(); configs.setValue(CourseLecturesFollowUpProvider.CONFIG_KEY_INVITATION_AFTER_DC_START_DAYS, invitationDays); diff --git a/src/main/java/org/olat/modules/quality/generator/provider/courselectures/ui/CourseLectureProviderConfigController.java b/src/main/java/org/olat/modules/quality/generator/provider/courselectures/ui/CourseLectureProviderConfigController.java index a484ac86da6..6d9300f1917 100644 --- a/src/main/java/org/olat/modules/quality/generator/provider/courselectures/ui/CourseLectureProviderConfigController.java +++ b/src/main/java/org/olat/modules/quality/generator/provider/courselectures/ui/CourseLectureProviderConfigController.java @@ -84,6 +84,7 @@ public class CourseLectureProviderConfigController extends ProviderConfigControl private SingleSelection surveyLectureStartEl; private TextElement surveyLectureEl; private TextElement minutesBeforeEndEl; + private TextElement announcementCoachDaysEl; private TextElement invitationDaysEl; private TextElement reminder1DaysEl; private TextElement reminder2DaysEl; @@ -144,6 +145,9 @@ public class CourseLectureProviderConfigController extends ProviderConfigControl durationEl = uifactory.addTextElement("config.duration", 4, duration, formLayout); // reminders + String announcementCoachDays = configs.getValue(CourseLecturesProvider.CONFIG_KEY_ANNOUNCEMENT_COACH_DAYS); + announcementCoachDaysEl = uifactory.addTextElement("config.announcement.coach.days", 4, announcementCoachDays, formLayout); + String invitationDays = configs.getValue(CourseLecturesProvider.CONFIG_KEY_INVITATION_AFTER_DC_START_DAYS); invitationDaysEl = uifactory.addTextElement("config.invitation.days", 4, invitationDays, formLayout); @@ -184,6 +188,7 @@ public class CourseLectureProviderConfigController extends ProviderConfigControl surveyLectureStartEl.setEnabled(enabled); surveyLectureEl.setEnabled(enabled); minutesBeforeEndEl.setEnabled(enabled); + announcementCoachDaysEl.setEnabled(enabled); invitationDaysEl.setEnabled(enabled); reminder1DaysEl.setEnabled(enabled); reminder2DaysEl.setEnabled(enabled); @@ -209,6 +214,7 @@ public class CourseLectureProviderConfigController extends ProviderConfigControl allOk &= validateIsMandatory(surveyLectureEl) && validateInteger(surveyLectureEl, 1, 10000); allOk &= validateIsMandatory(minutesBeforeEndEl) && validateInteger(minutesBeforeEndEl, 0, 1000); allOk &= validateIsMandatory(durationEl) && validateInteger(durationEl, 1, 10000); + allOk &= validateInteger(announcementCoachDaysEl, 0, 10000); allOk &= validateInteger(invitationDaysEl, 0, 10000); allOk &= validateInteger(reminder1DaysEl, 1, 10000); allOk &= validateInteger(reminder2DaysEl, 1, 10000); @@ -242,6 +248,7 @@ public class CourseLectureProviderConfigController extends ProviderConfigControl surveyLectureEl.clearError(); minutesBeforeEndEl.clearError(); durationEl.clearError(); + announcementCoachDaysEl.clearError(); invitationDaysEl.clearError(); reminder1DaysEl.clearError(); reminder2DaysEl.clearError(); @@ -285,6 +292,9 @@ public class CourseLectureProviderConfigController extends ProviderConfigControl String duration = durationEl.getValue(); configs.setValue(CourseLecturesProvider.CONFIG_KEY_DURATION_DAYS, duration); + + String announcementCoachDays = announcementCoachDaysEl.getValue(); + configs.setValue(CourseLecturesProvider.CONFIG_KEY_ANNOUNCEMENT_COACH_DAYS, announcementCoachDays); String invitationDays = invitationDaysEl.getValue(); configs.setValue(CourseLecturesProvider.CONFIG_KEY_INVITATION_AFTER_DC_START_DAYS, invitationDays); diff --git a/src/main/java/org/olat/modules/quality/generator/provider/courselectures/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/modules/quality/generator/provider/courselectures/ui/_i18n/LocalStrings_de.properties index b487746ef1b..1257fea0bdc 100644 --- a/src/main/java/org/olat/modules/quality/generator/provider/courselectures/ui/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/modules/quality/generator/provider/courselectures/ui/_i18n/LocalStrings_de.properties @@ -1,3 +1,4 @@ +config.announcement.coach.days=Ank\u00FCndigung f\u00FCr Betreuer (Tage) config.duration=Dauer der Datenerhebung (Tage) config.invitation.days=Einladung (Tage nach Start der Datenerhebung) config.lectures.total.max=Maximale Anzahl Lektionen des Betreuers diff --git a/src/main/java/org/olat/modules/quality/generator/provider/courselectures/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/modules/quality/generator/provider/courselectures/ui/_i18n/LocalStrings_en.properties index e680d76932e..1699b93ced7 100644 --- a/src/main/java/org/olat/modules/quality/generator/provider/courselectures/ui/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/modules/quality/generator/provider/courselectures/ui/_i18n/LocalStrings_en.properties @@ -1,4 +1,5 @@ #Tue Sep 11 22:17:57 CEST 2018 +config.announcement.coach.days=Announcement to coaches (days) config.duration=Duration of the data collection (days) config.invitation.days=Invitation (days after data collection start) config.lectures.total.max=Teacher maximum lectures @@ -23,7 +24,7 @@ config.topic=Topic error.lectures.min.higher.max=The maximum number of lectures has to be higher than the minimum number of lectures. error.number.greater=$org.olat.modules.quality.ui\:error.number.greater error.number.lower=$org.olat.modules.quality.ui\:error.number.lower -error.wrong.number=$org.olat.modules.quality.ui\:error.wrong.number.wrong.number +error.wrong.number=$org.olat.modules.quality.ui\:error.wrong.number followup.config.previous=Erstbefragung followup.config.single.check=Data collection, if average of a question is followup.config.single.limit=Limit single rating diff --git a/src/main/java/org/olat/modules/quality/manager/QualityContextDAO.java b/src/main/java/org/olat/modules/quality/manager/QualityContextDAO.java index 8338d27001b..1a712f70aa1 100644 --- a/src/main/java/org/olat/modules/quality/manager/QualityContextDAO.java +++ b/src/main/java/org/olat/modules/quality/manager/QualityContextDAO.java @@ -23,9 +23,9 @@ import java.util.Collections; import java.util.Date; import java.util.List; +import org.apache.logging.log4j.Logger; import org.olat.core.commons.persistence.DB; import org.olat.core.commons.persistence.QueryBuilder; -import org.apache.logging.log4j.Logger; import org.olat.core.logging.Tracing; import org.olat.modules.curriculum.CurriculumElement; import org.olat.modules.curriculum.CurriculumElementRef; @@ -255,6 +255,23 @@ class QualityContextDAO { .getResultList(); } + List<EvaluationFormParticipation> loadParticipationByRole(QualityDataCollectionRef dataCollectionRef, + QualityContextRole role) { + if (dataCollectionRef == null || dataCollectionRef.getKey() == null) return Collections.emptyList(); + + StringBuilder sb = new StringBuilder(256); + sb.append("select context.evaluationFormParticipation"); + sb.append(" from qualitycontext as context"); + sb.append(" where context.dataCollection.key = :dataCollectionKey"); + sb.append(" and context.role = :roleName"); + + return dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), EvaluationFormParticipation.class) + .setParameter("dataCollectionKey", dataCollectionRef.getKey()) + .setParameter("roleName", role) + .getResultList(); + } + public boolean hasContexts(EvaluationFormParticipationRef participationRef) { if (participationRef == null || participationRef.getKey() == null) return false; diff --git a/src/main/java/org/olat/modules/quality/manager/QualityMailing.java b/src/main/java/org/olat/modules/quality/manager/QualityMailing.java index 251040ecb8b..c5a1145b415 100644 --- a/src/main/java/org/olat/modules/quality/manager/QualityMailing.java +++ b/src/main/java/org/olat/modules/quality/manager/QualityMailing.java @@ -68,6 +68,7 @@ import org.olat.modules.forms.EvaluationFormParticipation; import org.olat.modules.forms.EvaluationFormPrintSelection; import org.olat.modules.forms.EvaluationFormSession; import org.olat.modules.forms.EvaluationFormSurvey; +import org.olat.modules.forms.EvaluationFormSurveyIdentifier; import org.olat.modules.forms.Figures; import org.olat.modules.forms.RubricRating; import org.olat.modules.forms.RubricStatistic; @@ -90,6 +91,7 @@ import org.olat.modules.quality.QualityExecutorParticipation; import org.olat.modules.quality.QualityExecutorParticipationSearchParams; import org.olat.modules.quality.QualityModule; import org.olat.modules.quality.QualityReminder; +import org.olat.modules.quality.QualityService; import org.olat.modules.quality.model.QualityMailTemplateBuilder; import org.olat.modules.quality.ui.FiguresFactory; import org.olat.modules.quality.ui.QualityMainController; @@ -120,6 +122,8 @@ class QualityMailing { @Autowired private QualityModule qualityModule; @Autowired + private QualityService qualityService; + @Autowired private QualityParticipationDAO participationDao; @Autowired private QualityDataCollectionDAO dataCollectionDao; @@ -131,11 +135,62 @@ class QualityMailing { private PdfService pdfService; @Autowired private I18nModule i18nModule; + + public void sendAnnouncementMail(QualityReminder reminder, Identity topicIdentity) { + MailTemplate template = createAnnouncementMailTemplate(reminder, topicIdentity); + + MailerResult result = new MailerResult(); + MailManager mailManager = CoreSpringFactory.getImpl(MailManager.class); + MailBundle bundle = mailManager.makeMailBundle(null, topicIdentity, template, null, null, result); + if(bundle != null) { + appendMimeFrom(bundle); + result = mailManager.sendMessage(bundle); + logMailerResult(result, reminder, topicIdentity); + } + } + + private MailTemplate createAnnouncementMailTemplate(QualityReminder reminder, Identity topicIdentity) { + User user = topicIdentity.getUser(); + Locale locale = I18nManager.getInstance().getLocaleOrDefault(user.getPreferences().getLanguage()); + Translator translator = Util.createPackageTranslator(QualityMainController.class, locale); + + String subject = translator.translate(reminder.getType().getSubjectI18nKey()); + String body = translator.translate(reminder.getType().getBodyI18nKey()); + QualityMailTemplateBuilder mailBuilder = QualityMailTemplateBuilder.builder(subject, body, locale); + + QualityDataCollection dataCollection = reminder.getDataCollection(); + mailBuilder.withStart(dataCollection.getStart()) + .withDeadline(dataCollection.getDeadline()) + .withTopicType(translator.translate(QualityDataCollectionTopicType.IDENTIY.getI18nKey())) + .withTopic(user.getFirstName() + " " + user.getLastName()) + .withTitle(dataCollection.getTitle()); + + EvaluationFormSurvey survey = qualityService.loadSurvey(dataCollection); + EvaluationFormSurvey previous = survey.getSeriesPrevious(); + if (previous != null) { + EvaluationFormSurveyIdentifier identifier = previous.getIdentifier(); + Long key = identifier.getOLATResourceable().getResourceableId(); + QualityDataCollection previousDC = qualityService.loadDataCollectionByKey(() -> key); + if (previousDC != null) { + mailBuilder.withPreviousTitle(previousDC.getTitle()); + } + } + + String seriePorition = previous != null + ? translator.translate("reminder.serie.followup") + : translator.translate("reminder.serie.primary"); + mailBuilder.withSeriePosition(seriePorition); + + String surveyContext = createDatCollectionContext(dataCollection, locale); + mailBuilder.withContext(surveyContext); + + return mailBuilder.build(); + } void sendReminderMail(QualityReminder reminder, QualityReminder invitation, EvaluationFormParticipation participation) { if (participation.getExecutor() != null) { Identity executor = participation.getExecutor(); - MailTemplate template = createReminderMailTemplate(reminder, invitation, executor); + MailTemplate template = createAnnouncementMailTemplate(reminder, invitation, executor); MailerResult result = new MailerResult(); MailManager mailManager = CoreSpringFactory.getImpl(MailManager.class); @@ -143,18 +198,22 @@ class QualityMailing { if(bundle != null) { appendMimeFrom(bundle); result = mailManager.sendMessage(bundle); - if (result.isSuccessful()) { - log.info(MessageFormat.format("{0} for quality data collection [key={1}] sent to {2}", - reminder.getType().name(), reminder.getDataCollection().getKey(), executor)); - } else { - log.warn(MessageFormat.format("Sending {0} for quality data collection [key={1}] to {2} failed: {3}", - reminder.getType().name(), reminder.getDataCollection().getKey(), executor, result.getErrorMessage())); - } + logMailerResult(result, reminder, executor); } } } - private MailTemplate createReminderMailTemplate(QualityReminder reminder, QualityReminder invitationReminder, Identity executor) { + private void logMailerResult(MailerResult result, QualityReminder reminder, Identity executor) { + if (result.isSuccessful()) { + log.info(MessageFormat.format("{0} for quality data collection [key={1}] sent to {2}", + reminder.getType().name(), reminder.getDataCollection().getKey(), executor)); + } else { + log.warn(MessageFormat.format("Sending {0} for quality data collection [key={1}] to {2} failed: {3}", + reminder.getType().name(), reminder.getDataCollection().getKey(), executor, result.getErrorMessage())); + } + } + + private MailTemplate createAnnouncementMailTemplate(QualityReminder reminder, QualityReminder invitationReminder, Identity executor) { Locale locale = I18nManager.getInstance().getLocaleOrDefault(executor.getUser().getPreferences().getLanguage()); Translator translator = Util.createPackageTranslator(QualityMainController.class, locale); @@ -198,8 +257,10 @@ class QualityMailing { String surveyContext = createParticipationContext(participation, locale); mailBuilder.withContext(surveyContext); } - - mailBuilder.withInvitation(invitationReminder.getSendDone()); + + if (invitationReminder != null) { + mailBuilder.withInvitation(invitationReminder.getSendDone()); + } return mailBuilder.build(); } @@ -476,5 +537,4 @@ class QualityMailing { } - } diff --git a/src/main/java/org/olat/modules/quality/manager/QualityReminderDAO.java b/src/main/java/org/olat/modules/quality/manager/QualityReminderDAO.java index 1b71d7853fb..3f7030647ac 100644 --- a/src/main/java/org/olat/modules/quality/manager/QualityReminderDAO.java +++ b/src/main/java/org/olat/modules/quality/manager/QualityReminderDAO.java @@ -83,6 +83,20 @@ public class QualityReminderDAO { return reminder; } + List<QualityReminder> load(QualityDataCollectionRef dataCollectionRef) { + if (dataCollectionRef == null || dataCollectionRef.getKey() == null) return Collections.emptyList(); + + StringBuilder sb = new StringBuilder(256); + sb.append("select reminder"); + sb.append(" from qualityreminder as reminder"); + sb.append(" where reminder.dataCollection.key = :dataCollectionKey"); + + return dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), QualityReminder.class) + .setParameter("dataCollectionKey", dataCollectionRef.getKey()) + .getResultList(); + } + QualityReminder load(QualityDataCollectionRef dataCollectionRef, QualityReminderType type) { if (dataCollectionRef == null || dataCollectionRef.getKey() == null || type == null) return null; @@ -108,8 +122,18 @@ public class QualityReminderDAO { sb.append(" from qualityreminder as reminder"); sb.append(" join fetch reminder.dataCollection as collection"); sb.append(" where reminder.sendDone is null"); - sb.append(" and collection.status = '").append(QualityDataCollectionStatus.RUNNING).append("'"); sb.append(" and reminder.sendPlaned <= :until"); + sb.append(" and ("); + append(sb, QualityReminderType.ANNOUNCEMENT_COACH_CONTEXT, QualityDataCollectionStatus.READY); + sb.append(" or"); + append(sb, QualityReminderType.ANNOUNCEMENT_COACH_TOPIC, QualityDataCollectionStatus.READY); + sb.append(" or"); + append(sb, QualityReminderType.INVITATION, QualityDataCollectionStatus.RUNNING); + sb.append(" or"); + append(sb, QualityReminderType.REMINDER1, QualityDataCollectionStatus.RUNNING); + sb.append(" or"); + append(sb, QualityReminderType.REMINDER2, QualityDataCollectionStatus.RUNNING); + sb.append(" )"); return dbInstance.getCurrentEntityManager() .createQuery(sb.toString(), QualityReminder.class) @@ -117,6 +141,10 @@ public class QualityReminderDAO { .getResultList(); } + private void append(StringBuilder sb, QualityReminderType type, QualityDataCollectionStatus status) { + sb.append("( reminder.type = '").append(type).append("' and collection.status = '").append(status).append("')"); + } + void delete(QualityReminder reminder) { if (reminder == null || reminder.getKey() == null) return; diff --git a/src/main/java/org/olat/modules/quality/manager/QualityServiceImpl.java b/src/main/java/org/olat/modules/quality/manager/QualityServiceImpl.java index eae6559854e..6e9100a401f 100644 --- a/src/main/java/org/olat/modules/quality/manager/QualityServiceImpl.java +++ b/src/main/java/org/olat/modules/quality/manager/QualityServiceImpl.java @@ -73,6 +73,7 @@ import org.olat.modules.forms.model.xml.Rubric; import org.olat.modules.quality.QualityContext; import org.olat.modules.quality.QualityContextBuilder; import org.olat.modules.quality.QualityContextRef; +import org.olat.modules.quality.QualityContextRole; import org.olat.modules.quality.QualityDataCollection; import org.olat.modules.quality.QualityDataCollectionLight; import org.olat.modules.quality.QualityDataCollectionRef; @@ -552,6 +553,11 @@ public class QualityServiceImpl return reminderDao.updateDatePlaned(reminder, datePlaned); } + @Override + public List<QualityReminder> loadReminders(QualityDataCollectionRef dataCollectionRef){ + return reminderDao.load(dataCollectionRef); + } + @Override public QualityReminder loadReminder(QualityDataCollectionRef dataCollectionRef, QualityReminderType type) { return reminderDao.load(dataCollectionRef, type); @@ -563,12 +569,16 @@ public class QualityServiceImpl } @Override - public void sendReminders(Date until) { + public void sendReminders(Date until) { Collection<QualityReminder> reminders = reminderDao.loadPending(until); log.debug("Send emails for quality remiders. Number of pending reminders: " + reminders.size()); for (QualityReminder reminder: reminders) { try { - sendReminder(reminder); + if (QualityReminderType.ANNOUNCEMENT_COACH_TOPIC == reminder.getType()) { + sendAnnouncementTopicIdentity(reminder); + } else { + sendReminder(reminder); + } reminderDao.updateDateDone(reminder, until); } catch (Exception e) { log.error("Send reminder of quality data collection failed!" + reminder.toString(), e); @@ -576,6 +586,13 @@ public class QualityServiceImpl } } + private void sendAnnouncementTopicIdentity(QualityReminder reminder) { + Identity topicIdentity = reminder.getDataCollection().getTopicIdentity(); + if (topicIdentity != null) { + qualityMailing.sendAnnouncementMail(reminder, topicIdentity); + } + } + private void sendReminder(QualityReminder reminder) { QualityReminder invitation = getInvitation(reminder); List<EvaluationFormParticipation> participations = getParticipants(reminder); @@ -592,6 +609,17 @@ public class QualityServiceImpl } private List<EvaluationFormParticipation> getParticipants(QualityReminder reminder) { + if (QualityReminderType.ANNOUNCEMENT_COACH_CONTEXT == reminder.getType()) { + return getContextCaches(reminder); + } + return getSurveyParticipants(reminder); + } + + private List<EvaluationFormParticipation> getContextCaches(QualityReminder reminder) { + return contextDao.loadParticipationByRole(reminder.getDataCollection(), QualityContextRole.coach); + } + + private List<EvaluationFormParticipation> getSurveyParticipants(QualityReminder reminder) { QualityReminderType type = reminder.getType(); EvaluationFormParticipationStatus status = type.getParticipationStatus(); QualityDataCollection dataCollection = reminder.getDataCollection(); diff --git a/src/main/java/org/olat/modules/quality/ui/RemindersController.java b/src/main/java/org/olat/modules/quality/ui/RemindersController.java index 61c570b6e84..ef761a4f764 100644 --- a/src/main/java/org/olat/modules/quality/ui/RemindersController.java +++ b/src/main/java/org/olat/modules/quality/ui/RemindersController.java @@ -20,6 +20,9 @@ package org.olat.modules.quality.ui; import java.util.Date; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.form.flexible.FormItemContainer; @@ -43,6 +46,8 @@ import org.springframework.beans.factory.annotation.Autowired; */ public class RemindersController extends FormBasicController { + private DateChooser announcementCoachTopicEl; + private DateChooser announcementCoachContextEl; private DateChooser invitationEl; private DateChooser reminder1El; private DateChooser reminder2El; @@ -51,6 +56,8 @@ public class RemindersController extends FormBasicController { private DataCollectionSecurityCallback secCallback; private QualityDataCollection dataCollection; + private QualityReminder announcementCoachTopic; + private QualityReminder announcementCoachContext; private QualityReminder invitation; private QualityReminder reminder1; private QualityReminder reminder2; @@ -63,20 +70,46 @@ public class RemindersController extends FormBasicController { super(ureq, wControl); this.secCallback = secCallback; this.dataCollection = dataCollection; - this.invitation = qualityService.loadReminder(dataCollection, QualityReminderType.INVITATION); - this.reminder1 = qualityService.loadReminder(dataCollection, QualityReminderType.REMINDER1); - this.reminder2 = qualityService.loadReminder(dataCollection, QualityReminderType.REMINDER2); + Map<QualityReminderType, QualityReminder> typeToReminder = qualityService.loadReminders(dataCollection).stream() + .collect(Collectors.toMap(QualityReminder::getType, Function.identity())); + this.announcementCoachTopic = typeToReminder.get(QualityReminderType.ANNOUNCEMENT_COACH_TOPIC); + this.announcementCoachContext = typeToReminder.get(QualityReminderType.ANNOUNCEMENT_COACH_CONTEXT); + this.invitation = typeToReminder.get(QualityReminderType.INVITATION); + this.reminder1 = typeToReminder.get(QualityReminderType.REMINDER1); + this.reminder2 = typeToReminder.get(QualityReminderType.REMINDER2); initForm(ureq); } @Override protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + if (announcementCoachContext != null) { + Date announcementCoachContextDate = announcementCoachContext.isSent() + ? announcementCoachContext.getSendDone() + : announcementCoachContext.getSendPlaned(); + announcementCoachContextEl = uifactory.addDateChooser("reminder.announcement.coach.context.date", + announcementCoachContextDate, formLayout); + announcementCoachContextEl.setDateChooserTimeEnabled(true); + announcementCoachContextEl.setEnabled(false); + } + + if (announcementCoachTopic != null) { + Date announcementCoachTopicDate = announcementCoachTopic.isSent() + ? announcementCoachTopic.getSendDone() + : announcementCoachTopic.getSendPlaned(); + announcementCoachTopicEl = uifactory.addDateChooser("reminder.announcement.coach.topic.date", + announcementCoachTopicDate, formLayout); + announcementCoachTopicEl.setDateChooserTimeEnabled(true); + announcementCoachTopicEl.setEnabled(false); + } + Date invitationDate = invitation != null? invitation.getSendPlaned(): null; invitationEl = uifactory.addDateChooser("reminder.invitation.date", invitationDate, formLayout); invitationEl.setDateChooserTimeEnabled(true); + Date reminder1Date = reminder1 != null? reminder1.getSendPlaned(): null; reminder1El = uifactory.addDateChooser("reminder.reminder1.date", reminder1Date, formLayout); reminder1El.setDateChooserTimeEnabled(true); + Date reminder2Date = reminder2 != null? reminder2.getSendPlaned(): null; reminder2El = uifactory.addDateChooser("reminder.reminder2.date", reminder2Date, formLayout); reminder2El.setDateChooserTimeEnabled(true); diff --git a/src/main/java/org/olat/modules/quality/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/modules/quality/ui/_i18n/LocalStrings_de.properties index 94e3be847ce..ffd7b6d505c 100644 --- a/src/main/java/org/olat/modules/quality/ui/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/modules/quality/ui/_i18n/LocalStrings_de.properties @@ -171,6 +171,12 @@ participation.user.curele.add.role.participant=$\:participation.role.participant participation.user.curele.add.roles=Rollen participation.user.curele.add.title=Teinehmer hinzuf\u00FCgen relation.right.selectableQualityReportAccess=Zugriff Qualit\u00E4tsmanagementreport +reminder.announcement.coach.context.body=<h1>$title</h1>Liebe/r $firstname $lastname<br/><br/>Gerne informieren wir Sie, dass am $start Ihre Beurteilung Ihrer Klasse ansteht. Wir bitten Sie, sich f\u00FCr diese Befragung Zeit einzuplanen.<br/><br/><strong>Beurteilungsgegenstand\: $topictype $topic</strong>$context<br/><br/>Besten Dank f\u00FCr Ihre Mitarbeit und einen sch\u00F6nen Tag.<br/><br/>Freundliche Gr\u00FCsse<br/>Ihr QM Officer +reminder.announcement.coach.context.date=Versanddatum Ank\u00FCndigung +reminder.announcement.coach.context.subject=Ank\u00FCndigung Datenerhebung: $title +reminder.announcement.coach.topic.body=<h1>$title</h1>Liebe/r $firstname $lastname<br/><br/>Gerne informieren wir Sie, dass am $start die Beurteilung Ihrer Klasse \u00FCber Ihren Unterricht ansteht. Wir bitten Sie, sich f\u00FCr diese Befragung Zeit einzuplanen.<br/><br/><strong>Beurteilungsgegenstand\: $topictype $topic</strong>$context<br/><br/>Besten Dank f\u00FCr Ihre Mitarbeit und einen sch\u00F6nen Tag.<br/><br/>Freundliche Gr\u00FCsse<br/>Ihr QM Officer +reminder.announcement.coach.topic.date=Versanddatum Ank\u00FCndigung +reminder.announcement.coach.topic.subject=Ank\u00FCndigung Datenerhebung: $title reminder.invitation.body=<h1>$title</h1>Liebe/r $firstname $lastname<br/><br/>Hiermit laden wir Sie ein, an der Befragung "$title" teilzunehmen.<br/><br/><div class\="o_email_button_group"><a class\="o_email_button" href\="$url">Umfrage \u00F6ffnen</a></div><br/><br/>Mit Ihren Antworten helfen Sie uns, dass wir unser Angebot als Bildungseinrichtung stetig verbessern k\u00F6nnen.<br/><br/><strong>Beurteilungsgegenstand\: $topictype $topic</strong>$context<br/><br/>Die Umfrage endet am <strong>$deadline</strong>. Nach diesem Zeitpunkt kann nicht mehr an der Befragung teilgenommen werden.<br/><br/>Besten Dank\!<br/><br/>Freundliche Gr\u00FCsse<br/>Ihr QM Officer reminder.invitation.date=Versanddatum Einladung reminder.invitation.subject=$title\: $topictype $topic diff --git a/src/main/java/org/olat/modules/quality/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/modules/quality/ui/_i18n/LocalStrings_en.properties index 054d27dd11c..52215712051 100644 --- a/src/main/java/org/olat/modules/quality/ui/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/modules/quality/ui/_i18n/LocalStrings_en.properties @@ -171,6 +171,12 @@ participation.user.curele.add.role.participant=$\:participation.role.participant participation.user.curele.add.roles=Roles participation.user.curele.add.title=Add participants relation.right.selectableQualityReportAccess=Quality management report access +reminder.announcement.coach.context.body=<h1>$title</h1>Dear $firstname $lastname<br/><br/>We are pleased to inform you that your class evaluation is due at $start. We kindly ask you to allow time for this survey.<br/><br/><strong>Topic\: $topictype $topic</strong>$context<br/><br/>Thank you very much for your cooperation and have a nice day.<br/><br/>Kind regards<br/>Your QM officer +reminder.announcement.coach.context.date=Delivery date announcement +reminder.announcement.coach.context.subject=Announcement data collection: $title +reminder.announcement.coach.topic.body=<h1>$title</h1>Dear $firstname $lastname<br/><br/>We are pleased to inform you that at $start the survey of your class about your lessons is pending. We kindly ask you to allow time for this survey.<br/><br/><strong>Topic\: $topictype $topic</strong>$context<br/><br/>Thank you very much for your cooperation and have a nice day.<br/><br/>Kind regards<br/>Your QM officer +reminder.announcement.coach.topic.date=Delivery date announcement +reminder.announcement.coach.topic.subject=Announcement data collection: $title reminder.invitation.body=<h1>$title</h1>Dear $firstname $lastname<br/><br/>We kindly invite you to participate in the survey "$title".<br/><br/><div class="o_email_button_group"><a class="o_email_button" href="$url">Open survey</a></div><br/><br/>With your answers you will help us to continuously improve our offer as an educational institution.<br/><br/><strong>Topic\: $topictype $topic</strong>$context<br/><br/>The survey ends on <strong>$deadline</strong>. It is not possible to take part in the survey after this date.<br/><br/>Thank you\!<br/><br/>Kind regards<br/>Your QM officer reminder.invitation.date=Delivery date invitaion reminder.invitation.subject=$title\: $topictype $topic diff --git a/src/main/resources/database/mysql/alter_15_pre_6_to_15_pre_7.sql b/src/main/resources/database/mysql/alter_15_pre_6_to_15_pre_7.sql index fe24632067c..f3ef935af61 100644 --- a/src/main/resources/database/mysql/alter_15_pre_6_to_15_pre_7.sql +++ b/src/main/resources/database/mysql/alter_15_pre_6_to_15_pre_7.sql @@ -2,3 +2,6 @@ alter table o_as_entry add a_passed_original bit; alter table o_as_entry add a_passed_mod_date datetime; alter table o_as_entry add fk_identity_passed_mod bigint; + +-- Quality management +alter table o_qual_reminder modify column q_type varchar(65); diff --git a/src/main/resources/database/mysql/setupDatabase.sql b/src/main/resources/database/mysql/setupDatabase.sql index d1ab010bf5d..c79ee9d7da2 100644 --- a/src/main/resources/database/mysql/setupDatabase.sql +++ b/src/main/resources/database/mysql/setupDatabase.sql @@ -2259,7 +2259,7 @@ create table o_qual_reminder ( id bigint not null auto_increment, creationdate datetime not null, lastmodified datetime not null, - q_type varchar(20) not null, + q_type varchar(65) not null, q_send_planed datetime, q_send_done datetime, fk_data_collection bigint not null, diff --git a/src/main/resources/database/oracle/alter_15_pre_6_to_15_pre_7.sql b/src/main/resources/database/oracle/alter_15_pre_6_to_15_pre_7.sql index 64718ee02c4..1e8ee7b72c2 100644 --- a/src/main/resources/database/oracle/alter_15_pre_6_to_15_pre_7.sql +++ b/src/main/resources/database/oracle/alter_15_pre_6_to_15_pre_7.sql @@ -2,3 +2,6 @@ alter table o_as_entry add a_passed_original number; alter table o_as_entry add a_passed_mod_date date; alter table o_as_entry add fk_identity_passed_mod number(20); + +-- Quality management +alter table o_qual_reminder modify (q_type varchar2(65)); diff --git a/src/main/resources/database/oracle/setupDatabase.sql b/src/main/resources/database/oracle/setupDatabase.sql index 2cdd507e033..db33a3b0920 100644 --- a/src/main/resources/database/oracle/setupDatabase.sql +++ b/src/main/resources/database/oracle/setupDatabase.sql @@ -2187,7 +2187,7 @@ create table o_qual_reminder ( id number(20) GENERATED ALWAYS AS IDENTITY, creationdate date not null, lastmodified date not null, - q_type varchar2(20), + q_type varchar2(65), q_send_planed date, q_send_done date, fk_data_collection number(20) not null, diff --git a/src/main/resources/database/postgresql/alter_15_pre_6_to_15_pre_7.sql b/src/main/resources/database/postgresql/alter_15_pre_6_to_15_pre_7.sql index da82351d8c9..55e3c894379 100644 --- a/src/main/resources/database/postgresql/alter_15_pre_6_to_15_pre_7.sql +++ b/src/main/resources/database/postgresql/alter_15_pre_6_to_15_pre_7.sql @@ -2,3 +2,6 @@ alter table o_as_entry add a_passed_original bool; alter table o_as_entry add a_passed_mod_date timestamp; alter table o_as_entry add fk_identity_passed_mod int8; + +-- Qualiyt management +alter table o_qual_reminder alter column q_type type varchar(65); diff --git a/src/main/resources/database/postgresql/setupDatabase.sql b/src/main/resources/database/postgresql/setupDatabase.sql index c20f3e2607d..4686b6dcb7b 100644 --- a/src/main/resources/database/postgresql/setupDatabase.sql +++ b/src/main/resources/database/postgresql/setupDatabase.sql @@ -2150,7 +2150,7 @@ create table o_qual_reminder ( id bigserial, creationdate timestamp not null, lastmodified timestamp not null, - q_type varchar(20), + q_type varchar(65), q_send_planed timestamp, q_send_done timestamp, fk_data_collection bigint not null, diff --git a/src/test/java/org/olat/modules/quality/generator/provider/courselectures/CourseLecturesProviderTest.java b/src/test/java/org/olat/modules/quality/generator/provider/courselectures/CourseLecturesProviderTest.java index 14ad83c833a..02fc936475c 100644 --- a/src/test/java/org/olat/modules/quality/generator/provider/courselectures/CourseLecturesProviderTest.java +++ b/src/test/java/org/olat/modules/quality/generator/provider/courselectures/CourseLecturesProviderTest.java @@ -28,11 +28,13 @@ import java.util.Date; import java.util.GregorianCalendar; import java.util.List; +import org.assertj.core.api.SoftAssertions; import org.junit.Test; import org.olat.basesecurity.OrganisationService; import org.olat.core.commons.persistence.DB; import org.olat.core.id.Identity; import org.olat.core.id.Organisation; +import org.olat.core.util.DateUtils; import org.olat.modules.curriculum.Curriculum; import org.olat.modules.curriculum.CurriculumElement; import org.olat.modules.curriculum.CurriculumElementRef; @@ -40,6 +42,8 @@ import org.olat.modules.curriculum.CurriculumService; import org.olat.modules.lecture.LectureBlock; import org.olat.modules.lecture.LectureService; import org.olat.modules.quality.QualityDataCollection; +import org.olat.modules.quality.QualityReminder; +import org.olat.modules.quality.QualityReminderType; import org.olat.modules.quality.QualityService; import org.olat.modules.quality.generator.QualityGenerator; import org.olat.modules.quality.generator.QualityGeneratorConfigs; @@ -108,6 +112,62 @@ public class CourseLecturesProviderTest extends OlatTestCase { .containsExactlyInAnyOrder(courseOrganisation1, courseOrganisation2) .doesNotContain(defaultOrganisation); } + + @Test + public void shouldGenerateWithAnnouncementForwards() { + Date now = new Date(); + Date startDate = DateUtils.addDays(now, 3); + Date startEnd = DateUtils.addDays(now, 4); + RepositoryEntry courseEntry = createCourseWithThreeLectures(startDate, startEnd); + CurriculumElement element = createCurriculumElement(); + curriculumService.addRepositoryEntry(element, courseEntry, false); + + QualityGenerator generator = createGeneratorInDefaultOrganisation(); + String durationDays = "10"; + QualityGeneratorConfigs configs = createAfterSecondLectureConfigs(generator, durationDays, element); + configs.setValue(CourseLecturesProvider.CONFIG_KEY_ANNOUNCEMENT_COACH_DAYS, "5"); + + Date lastRun = DateUtils.addDays(now, -9); + Date to = DateUtils.addDays(now, 0); + + List<QualityDataCollection> generated = sut.generate(generator, configs, lastRun, to); + assertThat(generated).hasSize(1); + QualityDataCollection dataCollection = generated.get(0); + dbInstance.commitAndCloseSession(); + + SoftAssertions softly = new SoftAssertions(); + softly.assertThat(dataCollection.getStart()).isCloseTo(startEnd, 1000); + + QualityReminder reminder = qualityService.loadReminder(dataCollection, QualityReminderType.ANNOUNCEMENT_COACH_TOPIC); + softly.assertThat(reminder).isNotNull(); + softly.assertThat(reminder.getSendPlaned()).isCloseTo(DateUtils.addDays(now, -1), 1000); + softly.assertAll(); + } + + @Test + public void shouldnotGenerateAnnouncementIfStarted() { + Date now = new Date(); + Date startDate = DateUtils.addDays(now,-2); + Date startEnd = DateUtils.addDays(now, -1); + RepositoryEntry courseEntry = createCourseWithThreeLectures(startDate, startEnd); + CurriculumElement element = createCurriculumElement(); + curriculumService.addRepositoryEntry(element, courseEntry, false); + + QualityGenerator generator = createGeneratorInDefaultOrganisation(); + String durationDays = "10"; + QualityGeneratorConfigs configs = createAfterSecondLectureConfigs(generator, durationDays, element); + configs.setValue(CourseLecturesProvider.CONFIG_KEY_ANNOUNCEMENT_COACH_DAYS, "2"); + + Date lastRun = DateUtils.addDays(now, -9); + Date to = DateUtils.addDays(now, 0); + + List<QualityDataCollection> generated = sut.generate(generator, configs, lastRun, to); + QualityDataCollection dataCollection = generated.get(0); + dbInstance.commitAndCloseSession(); + + QualityReminder reminder = qualityService.loadReminder(dataCollection, QualityReminderType.ANNOUNCEMENT_COACH_TOPIC); + assertThat(reminder).isNull(); + } private RepositoryEntry createCourseWithThreeLectures(Date startDate, Date endDate) { Identity initialAuthor = JunitTestHelper.createAndPersistIdentityAsAuthor(JunitTestHelper.random()); diff --git a/src/test/java/org/olat/modules/quality/manager/QualityContextDAOTest.java b/src/test/java/org/olat/modules/quality/manager/QualityContextDAOTest.java index 51a3f1da1ab..9059fee20be 100644 --- a/src/test/java/org/olat/modules/quality/manager/QualityContextDAOTest.java +++ b/src/test/java/org/olat/modules/quality/manager/QualityContextDAOTest.java @@ -205,6 +205,27 @@ public class QualityContextDAOTest extends OlatTestCase { assertThat(reloadedContext.get(0)).isEqualTo(context); } + @Test + public void shouldLoadParticipationsByDataCollectionAndRole() { + QualityDataCollection dataCollection = qualityTestHelper.createDataCollection(); + QualityDataCollection dataCollectionOther = qualityTestHelper.createDataCollection(); + EvaluationFormParticipation coach1 = qualityTestHelper.createParticipation(); + sut.createContext(dataCollection, qualityTestHelper.createParticipation(), QualityContextRole.coach, null, null, null); + EvaluationFormParticipation coach2 = qualityTestHelper.createParticipation(); + sut.createContext(dataCollection, coach2, QualityContextRole.coach, null, null, null); + EvaluationFormParticipation participant = qualityTestHelper.createParticipation(); + sut.createContext(dataCollection, participant, QualityContextRole.participant, null, null, null); + EvaluationFormParticipation coachOther = qualityTestHelper.createParticipation(); + sut.createContext(dataCollectionOther, coachOther, QualityContextRole.coach, null, null, null); + dbInstance.commitAndCloseSession(); + + List<EvaluationFormParticipation> participations = sut.loadParticipationByRole(dataCollection, QualityContextRole.coach); + + assertThat(participations) + .containsExactlyInAnyOrder(coach1, coach2) + .doesNotContain(participant, coachOther); + } + @Test public void shouldCheckWhetherParticipationHasContexts() { QualityDataCollection dataCollection = qualityTestHelper.createDataCollection(); diff --git a/src/test/java/org/olat/modules/quality/manager/QualityReminderDAOTest.java b/src/test/java/org/olat/modules/quality/manager/QualityReminderDAOTest.java index d95dbada5c9..543ca6128c2 100644 --- a/src/test/java/org/olat/modules/quality/manager/QualityReminderDAOTest.java +++ b/src/test/java/org/olat/modules/quality/manager/QualityReminderDAOTest.java @@ -20,6 +20,15 @@ package org.olat.modules.quality.manager; import static org.assertj.core.api.Assertions.assertThat; +import static org.olat.modules.quality.QualityDataCollectionStatus.FINISHED; +import static org.olat.modules.quality.QualityDataCollectionStatus.PREPARATION; +import static org.olat.modules.quality.QualityDataCollectionStatus.READY; +import static org.olat.modules.quality.QualityDataCollectionStatus.RUNNING; +import static org.olat.modules.quality.QualityReminderType.ANNOUNCEMENT_COACH_CONTEXT; +import static org.olat.modules.quality.QualityReminderType.ANNOUNCEMENT_COACH_TOPIC; +import static org.olat.modules.quality.QualityReminderType.INVITATION; +import static org.olat.modules.quality.QualityReminderType.REMINDER1; +import static org.olat.modules.quality.QualityReminderType.REMINDER2; import java.util.Date; import java.util.GregorianCalendar; @@ -97,6 +106,22 @@ public class QualityReminderDAOTest extends OlatTestCase { assertThat(reminder.isSent()).isTrue(); } + @Test + public void shouldLoadReminderByDataCollection() { + Date sendDate = new Date(); + QualityDataCollectionRef dataCollectionRef = qualityTestHelper.createDataCollection(); + QualityReminder reminder1 = sut.create(dataCollectionRef, sendDate, QualityReminderType.ANNOUNCEMENT_COACH_TOPIC); + QualityReminder reminder2 = sut.create(dataCollectionRef, sendDate, QualityReminderType.INVITATION); + QualityReminder reminderOther = sut.create(qualityTestHelper.createDataCollection(), sendDate, QualityReminderType.INVITATION); + dbInstance.commitAndCloseSession(); + + List<QualityReminder> reminders = sut.load(dataCollectionRef); + + assertThat(reminders) + .containsExactlyInAnyOrder(reminder1, reminder2) + .doesNotContain(reminderOther); + } + @Test public void shouldLoadReminderByDataCollectionAndType() { Date sendDate = new Date(); @@ -138,29 +163,64 @@ public class QualityReminderDAOTest extends OlatTestCase { } @Test - public void shouldLoadPendingOnlyIfDataCollectionIsRunning() { + public void shouldLoadPendingDependingOnStatus() { Date until = (new GregorianCalendar(2013,1,28,1,1,1)).getTime(); Date beforeUntil = (new GregorianCalendar(2013,1,26,2,1,1)).getTime(); - QualityReminderType type = QualityReminderType.REMINDER1; - QualityDataCollection dataCollectionPreaparation = qualityTestHelper.createDataCollection(); - qualityTestHelper.updateStatus(dataCollectionPreaparation, QualityDataCollectionStatus.PREPARATION); - QualityReminder reminderPreparation = sut.create(dataCollectionPreaparation, beforeUntil, type); - QualityDataCollection dataCollectionReady = qualityTestHelper.createDataCollection(); - qualityTestHelper.updateStatus(dataCollectionReady, QualityDataCollectionStatus.READY); - QualityReminder reminderReady = sut.create(dataCollectionReady, beforeUntil, type); - QualityDataCollection dataCollectionRunning = qualityTestHelper.createDataCollection(); - qualityTestHelper.updateStatus(dataCollectionRunning, QualityDataCollectionStatus.RUNNING); - QualityReminder reminderRunning = sut.create(dataCollectionRunning, beforeUntil, type); - QualityDataCollection dataCollectionFinished = qualityTestHelper.createDataCollection(); - qualityTestHelper.updateStatus(dataCollectionFinished, QualityDataCollectionStatus.FINISHED); - QualityReminder reminderFinished = sut.create(dataCollectionFinished, beforeUntil, type); + QualityReminder announcementCoachTopicPreparation = createDcReminder(beforeUntil, ANNOUNCEMENT_COACH_TOPIC, PREPARATION); + QualityReminder announcementCoachTopicReady = createDcReminder(beforeUntil, ANNOUNCEMENT_COACH_TOPIC, READY); + QualityReminder announcementCoachTopicRunning = createDcReminder(beforeUntil, ANNOUNCEMENT_COACH_TOPIC, RUNNING); + QualityReminder announcementCoachTopicFinished = createDcReminder(beforeUntil, ANNOUNCEMENT_COACH_TOPIC, FINISHED); + QualityReminder announcementCoachContextPreparation = createDcReminder(beforeUntil, ANNOUNCEMENT_COACH_CONTEXT, PREPARATION); + QualityReminder announcementCoachContextReady = createDcReminder(beforeUntil, ANNOUNCEMENT_COACH_CONTEXT, READY); + QualityReminder announcementCoachContextRunning = createDcReminder(beforeUntil, ANNOUNCEMENT_COACH_CONTEXT, RUNNING); + QualityReminder announcementCoachContextFinished = createDcReminder(beforeUntil, ANNOUNCEMENT_COACH_CONTEXT, FINISHED); + QualityReminder invitationPreparation = createDcReminder(beforeUntil, INVITATION, PREPARATION); + QualityReminder invitationReady = createDcReminder(beforeUntil, INVITATION, READY); + QualityReminder invitationRunning = createDcReminder(beforeUntil, INVITATION, RUNNING); + QualityReminder invitationFinished = createDcReminder(beforeUntil, INVITATION, FINISHED); + QualityReminder reminder1Preparation = createDcReminder(beforeUntil, REMINDER1, PREPARATION); + QualityReminder reminder1Ready = createDcReminder(beforeUntil, REMINDER1, READY); + QualityReminder reminder1Running = createDcReminder(beforeUntil, REMINDER1, RUNNING); + QualityReminder reminder1Finished = createDcReminder(beforeUntil, REMINDER1, FINISHED); + QualityReminder reminder2Preparation = createDcReminder(beforeUntil, REMINDER2, PREPARATION); + QualityReminder reminder2Ready = createDcReminder(beforeUntil, REMINDER2, READY); + QualityReminder reminder2Running = createDcReminder(beforeUntil, REMINDER2, RUNNING); + QualityReminder reminder2Finished = createDcReminder(beforeUntil, REMINDER2, FINISHED); dbInstance.commitAndCloseSession(); List<QualityReminder> pending = sut.loadPending(until); assertThat(pending) - .containsExactlyInAnyOrder(reminderRunning) - .doesNotContain(reminderPreparation, reminderReady, reminderFinished); + .containsExactlyInAnyOrder( + announcementCoachTopicReady, + announcementCoachContextReady, + invitationRunning, + reminder1Running, + reminder2Running) + .doesNotContain( + announcementCoachTopicPreparation, + announcementCoachTopicRunning, + announcementCoachTopicFinished, + announcementCoachContextPreparation, + announcementCoachContextRunning, + announcementCoachContextFinished, + invitationPreparation, + invitationReady, + invitationFinished, + reminder1Preparation, + reminder1Ready, + reminder1Finished, + reminder2Preparation, + reminder2Preparation, + reminder2Ready, + reminder2Finished + ); + } + + private QualityReminder createDcReminder(Date sendDate, QualityReminderType type, QualityDataCollectionStatus status) { + QualityDataCollection dataCollection = qualityTestHelper.createDataCollection(); + qualityTestHelper.updateStatus(dataCollection, status); + return sut.create(dataCollection, sendDate, type); } @Test -- GitLab