From ee1633867815240640c1710ebecc7f23480bc207 Mon Sep 17 00:00:00 2001
From: Alicia de Dios Fuente <Alicia.DeDiosFuente@unige.ch>
Date: Mon, 17 Feb 2025 12:13:12 +0100
Subject: [PATCH 1/3] feat: [DLCM-2812] send email to request creator when
 request is processed

---
 .../ch/dlcm/business/NotificationService.java | 68 +++++++++++++++----
 .../business/OrganizationalUnitService.java   |  4 ++
 .../service/AdminEmailProcessingService.java  | 46 +++++++++++--
 .../service/NotificationsEmailService.java    | 10 ++-
 .../dlcm/task/NotificationsTaskRunnable.java  |  3 +-
 .../java/ch/dlcm/message/EmailMessage.java    |  7 +-
 .../NotificationSpecification.java            | 11 +++
 .../main/resources/scripts/upgrade22to30.sql  |  5 +-
 .../request_to_create_org_unit_accepted.html  | 48 +++++++++++++
 .../request_to_create_org_unit_refused.html   | 44 ++++++++++++
 .../request_to_dispose_archive_accepted.html  | 38 +++++++++++
 .../request_to_join_org_unit_accepted.html    | 48 +++++++++++++
 .../request_to_join_org_unit_refused.html     | 50 ++++++++++++++
 13 files changed, 359 insertions(+), 23 deletions(-)
 create mode 100644 DLCM-ResourceServerCommon/src/main/resources/email-templates/request_to_create_org_unit_accepted.html
 create mode 100644 DLCM-ResourceServerCommon/src/main/resources/email-templates/request_to_create_org_unit_refused.html
 create mode 100644 DLCM-ResourceServerCommon/src/main/resources/email-templates/request_to_dispose_archive_accepted.html
 create mode 100644 DLCM-ResourceServerCommon/src/main/resources/email-templates/request_to_join_org_unit_accepted.html
 create mode 100644 DLCM-ResourceServerCommon/src/main/resources/email-templates/request_to_join_org_unit_refused.html

diff --git a/DLCM-Admin/src/main/java/ch/dlcm/business/NotificationService.java b/DLCM-Admin/src/main/java/ch/dlcm/business/NotificationService.java
index 5cad874a9d..32f9a00a9a 100644
--- a/DLCM-Admin/src/main/java/ch/dlcm/business/NotificationService.java
+++ b/DLCM-Admin/src/main/java/ch/dlcm/business/NotificationService.java
@@ -51,6 +51,7 @@ import ch.unige.solidify.exception.SolidifyUnmodifiableException;
 import ch.unige.solidify.service.ResourceService;
 import ch.unige.solidify.util.StringTool;
 
+import ch.dlcm.DLCMConstants;
 import ch.dlcm.controller.AdminController;
 import ch.dlcm.message.EmailMessage;
 import ch.dlcm.model.StatusHistory;
@@ -283,19 +284,8 @@ public class NotificationService extends ResourceService<Notification> {
   @Override
   public Notification save(Notification item) {
 
-    // Send an email for notification of type access dataset request
-    if (item.getNotificationType().equals(NotificationType.ACCESS_DATASET_REQUEST)) {
-      EmailMessage emailMessage;
-      Map<String, Object> parameters = new HashMap<>();
-      parameters.put(item.getObjectId(), null);
-      if (Objects.requireNonNull(item.getNotificationStatus()) == NotificationStatus.APPROVED) {
-        emailMessage = new EmailMessage(item.getEmitter().getEmail(), EmailMessage.EmailTemplate.REQUEST_TO_ACCESS_DATASET_ACCEPTED, parameters);
-        SolidifyEventPublisher.getPublisher().publishEvent(Objects.requireNonNull(emailMessage));
-      } else if (item.getNotificationStatus() == NotificationStatus.REFUSED) {
-        emailMessage = new EmailMessage(item.getEmitter().getEmail(), EmailMessage.EmailTemplate.REQUEST_TO_ACCESS_DATASET_REFUSED, parameters);
-        SolidifyEventPublisher.getPublisher().publishEvent(Objects.requireNonNull(emailMessage));
-      }
-    }
+    // Send an email to emitter for certain types of notification
+    this.sendEmailToEmitter(item);
 
     // Status is non-applicable if notification category is info
     Optional<NotificationType> notificationType = this.notificationTypeRepository.findById(item.getNotificationType().getResId());
@@ -307,6 +297,58 @@ public class NotificationService extends ResourceService<Notification> {
     return super.save(item);
   }
 
+  private void sendEmailToEmitter(Notification notification) {
+    if ((notification.getNotificationType().equals(NotificationType.ACCESS_DATASET_REQUEST)
+            || notification.getNotificationType().equals(NotificationType.JOIN_ORGUNIT_REQUEST)
+            || notification.getNotificationType().equals(NotificationType.CREATE_ORGUNIT_REQUEST)
+            || notification.getNotificationType().equals(NotificationType.APPROVE_DISPOSAL_REQUEST)
+            || notification.getNotificationType().equals(NotificationType.APPROVE_DISPOSAL_BY_ORGUNIT_REQUEST))
+            && (notification.getNotificationStatus().equals(NotificationStatus.APPROVED)
+              || notification.getNotificationStatus().equals(NotificationStatus.REFUSED))) {
+      EmailMessage.EmailTemplate emailTemplate = this.getEmailTemplate(notification.getNotificationType(), notification.getNotificationStatus());
+      if (emailTemplate != null) {
+        Map<String, Object> parameters = new HashMap();
+        if (notification.getNotificationType().equals(NotificationType.JOIN_ORGUNIT_REQUEST)) {
+          parameters.put("objectId", notification.getNotifiedOrgUnit().getResId());
+        } else {
+          parameters.put("objectId", notification.getObjectId());
+        }
+        if (notification.getNotificationStatus().equals(NotificationStatus.REFUSED)) {
+          parameters.put(DLCMConstants.REASON, notification.getResponseMessage());
+        }
+        EmailMessage emailMessage = new EmailMessage(notification.getEmitter().getEmail(), emailTemplate, parameters);
+        SolidifyEventPublisher.getPublisher().publishEvent(Objects.requireNonNull(emailMessage));
+      }
+    }
+  }
+
+  private EmailMessage.EmailTemplate getEmailTemplate(NotificationType notificationType, NotificationStatus notificationStatus) {
+    Map<String, Map<NotificationStatus, EmailMessage.EmailTemplate>> templateMap = Map.of(
+            NotificationType.ACCESS_DATASET_REQUEST.getResId(), Map.of(
+                    NotificationStatus.APPROVED, EmailMessage.EmailTemplate.REQUEST_TO_ACCESS_DATASET_ACCEPTED,
+                    NotificationStatus.REFUSED, EmailMessage.EmailTemplate.REQUEST_TO_ACCESS_DATASET_REFUSED
+            ),
+            NotificationType.JOIN_ORGUNIT_REQUEST.getResId(), Map.of(
+                    NotificationStatus.APPROVED, EmailMessage.EmailTemplate.REQUEST_TO_JOIN_ORG_UNIT_ACCEPTED,
+                    NotificationStatus.REFUSED, EmailMessage.EmailTemplate.REQUEST_TO_JOIN_ORG_UNIT_REFUSED
+            ),
+            NotificationType.CREATE_ORGUNIT_REQUEST.getResId(), Map.of(
+                    NotificationStatus.APPROVED, EmailMessage.EmailTemplate.REQUEST_TO_CREATE_ORG_UNIT_ACCEPTED,
+                    NotificationStatus.REFUSED, EmailMessage.EmailTemplate.REQUEST_TO_CREATE_ORG_UNIT_REFUSED
+            ),
+            NotificationType.APPROVE_DISPOSAL_REQUEST.getResId(), Map.of(
+                    NotificationStatus.APPROVED, EmailMessage.EmailTemplate.REQUEST_TO_DISPOSE_ARCHIVE_ACCEPTED
+            ),
+            NotificationType.APPROVE_DISPOSAL_BY_ORGUNIT_REQUEST.getResId(), Map.of(
+                    NotificationStatus.APPROVED, EmailMessage.EmailTemplate.REQUEST_TO_DISPOSE_ARCHIVE_ACCEPTED
+            )
+    );
+
+    return Optional.ofNullable(templateMap.get(notificationType.getResId()))
+            .map(statusMap -> statusMap.get(notificationStatus))
+            .orElse(null);
+  }
+
   /*
    * Verify that each contributorsId correspond to an authenticated user and the type of notification is the type of
    * MY_INDIRECT_DEPOSIT_COMPLETED_INFO
diff --git a/DLCM-Admin/src/main/java/ch/dlcm/business/OrganizationalUnitService.java b/DLCM-Admin/src/main/java/ch/dlcm/business/OrganizationalUnitService.java
index 45ce92ef17..893827aaf9 100644
--- a/DLCM-Admin/src/main/java/ch/dlcm/business/OrganizationalUnitService.java
+++ b/DLCM-Admin/src/main/java/ch/dlcm/business/OrganizationalUnitService.java
@@ -80,6 +80,10 @@ public class OrganizationalUnitService extends CompositeResourceService<Organiza
     super.delete(id);
   }
 
+  public OrganizationalUnit findByName(String name) {
+    return ((OrganizationalUnitRepository)this.itemRepository).findByName(name);
+  }
+
   @Override
   public void validateItemSpecificRules(OrganizationalUnit item, BindingResult errors) {
 
diff --git a/DLCM-Admin/src/main/java/ch/dlcm/service/AdminEmailProcessingService.java b/DLCM-Admin/src/main/java/ch/dlcm/service/AdminEmailProcessingService.java
index da77f72438..aa8e8ec6a1 100644
--- a/DLCM-Admin/src/main/java/ch/dlcm/service/AdminEmailProcessingService.java
+++ b/DLCM-Admin/src/main/java/ch/dlcm/service/AdminEmailProcessingService.java
@@ -9,12 +9,12 @@
  * it under the terms of the GNU General Public License as
  * published by the Free Software Foundation, either version 2 of the
  * License, or (at your option) any later version.
- * 
+ *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
- * 
+ *
  * You should have received a copy of the GNU General Public
  * License along with this program.  If not, see
  * <http://www.gnu.org/licenses/gpl-2.0.html>.
@@ -23,6 +23,11 @@
 
 package ch.dlcm.service;
 
+import static ch.dlcm.message.EmailMessage.EmailTemplate.REQUEST_TO_ACCESS_DATASET_REFUSED;
+import static ch.dlcm.message.EmailMessage.EmailTemplate.REQUEST_TO_CREATE_ORG_UNIT_REFUSED;
+import static ch.dlcm.message.EmailMessage.EmailTemplate.REQUEST_TO_DISPOSE_ARCHIVE_ACCEPTED;
+import static ch.dlcm.message.EmailMessage.EmailTemplate.REQUEST_TO_JOIN_ORG_UNIT_REFUSED;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -47,6 +52,7 @@ import ch.dlcm.config.DLCMProperties;
 import ch.dlcm.controller.AdminController;
 import ch.dlcm.message.EmailMessage;
 import ch.dlcm.model.oais.ArchivalInfoPackage;
+import ch.dlcm.model.settings.OrganizationalUnit;
 import ch.dlcm.service.rest.trusted.TrustedArchivalInfoPackageRemoteResourceService;
 
 @Service
@@ -96,11 +102,39 @@ public class AdminEmailProcessingService extends EmailProcessingService {
           parameterList.put(ORG_UNITS_CONSTANT, emailMessage.getParameters());
           this.sendEmailWithOrgUnitsName(emailMessage);
         }
-        case REQUEST_TO_ACCESS_DATASET_ACCEPTED, REQUEST_TO_ACCESS_DATASET_REFUSED -> {
+        case REQUEST_TO_CREATE_ORG_UNIT_ACCEPTED, REQUEST_TO_CREATE_ORG_UNIT_REFUSED -> {
+          parameterList = this.getEmailDefaultParameters();
+          OrganizationalUnit one = this.organizationalUnitService.findByName(emailMessage.getParameters().get("objectId").toString());
+          parameterList.put("orgunit", one);
+          if (emailMessage.getTemplate().equals(REQUEST_TO_CREATE_ORG_UNIT_REFUSED)) {
+            parameterList.put(DLCMConstants.REASON, emailMessage.getParameters().get(DLCMConstants.REASON).toString());
+          }
+
+          EmailParameters emailParameters = new EmailParameters().setToList(Collections.singletonList(emailMessage.getTo()))
+                  .setSubject(emailMessage.getTemplate().getSubject()).setTemplate(emailMessage.getTemplate().toString().toLowerCase())
+                  .setTemplateParameters(parameterList).addCc(null).addBcc(null);
+          this.emailService.sendEmailWithTemplate(emailParameters);
+        }
+        case REQUEST_TO_JOIN_ORG_UNIT_ACCEPTED, REQUEST_TO_JOIN_ORG_UNIT_REFUSED -> {
+          parameterList = this.getEmailDefaultParameters();
+          OrganizationalUnit one = this.organizationalUnitService.findOne(emailMessage.getParameters().get("objectId").toString());
+          parameterList.put("orgunit", one);
+          if (emailMessage.getTemplate().equals(REQUEST_TO_JOIN_ORG_UNIT_REFUSED)) {
+            parameterList.put(DLCMConstants.REASON, emailMessage.getParameters().get(DLCMConstants.REASON).toString());
+          }
+          EmailParameters emailParameters = new EmailParameters().setToList(Collections.singletonList(emailMessage.getTo()))
+                  .setSubject(emailMessage.getTemplate().getSubject()).setTemplate(emailMessage.getTemplate().toString().toLowerCase())
+                  .setTemplateParameters(parameterList).addCc(null).addBcc(null);
+          this.emailService.sendEmailWithTemplate(emailParameters);
+        }
+        case REQUEST_TO_ACCESS_DATASET_ACCEPTED,
+             REQUEST_TO_ACCESS_DATASET_REFUSED,
+             REQUEST_TO_DISPOSE_ARCHIVE_ACCEPTED-> {
           parameterList = this.getEmailDefaultParameters();
-          for (String aip : emailMessage.getParameters().keySet()) {
-            ArchivalInfoPackage one = this.archivalInfoPackageRemoteResourceService.findOne(aip);
-            parameterList.put("aip", one); // we should have only one aip here
+          ArchivalInfoPackage one = this.archivalInfoPackageRemoteResourceService.findOne(emailMessage.getParameters().get("objectId").toString());
+          parameterList.put("aip", one);
+          if (emailMessage.getTemplate().equals(REQUEST_TO_ACCESS_DATASET_REFUSED)) {
+            parameterList.put(DLCMConstants.REASON, emailMessage.getParameters().get(DLCMConstants.REASON).toString());
           }
           EmailParameters emailParameters = new EmailParameters().setToList(Collections.singletonList(emailMessage.getTo()))
                   .setSubject(emailMessage.getTemplate().getSubject()).setTemplate(emailMessage.getTemplate().toString().toLowerCase())
diff --git a/DLCM-Admin/src/main/java/ch/dlcm/service/NotificationsEmailService.java b/DLCM-Admin/src/main/java/ch/dlcm/service/NotificationsEmailService.java
index 411cca8fec..c34f60da74 100644
--- a/DLCM-Admin/src/main/java/ch/dlcm/service/NotificationsEmailService.java
+++ b/DLCM-Admin/src/main/java/ch/dlcm/service/NotificationsEmailService.java
@@ -115,7 +115,8 @@ public class NotificationsEmailService extends DLCMService {
     for (Notification notificationToSend : notificationsToSend) {
 
       // keep the notification only if it has the specified filter status
-      if (notificationToSend.getNotificationStatus().equals(NotificationStatus.PENDING)) {
+      if (notificationToSend.getNotificationStatus().equals(NotificationStatus.PENDING)
+              || notificationToSend.getNotificationStatus().equals(NotificationStatus.NON_APPLICABLE)) {
         String recipientId = notificationToSend.getRecipient().getResId();
         notificationsByPersonId.computeIfAbsent(recipientId, s -> new ArrayList<>());
         notificationsByPersonId.get(recipientId).add(notificationToSend);
@@ -185,6 +186,13 @@ public class NotificationsEmailService extends DLCMService {
         parameters.put(n.getEmitter().getPerson().getResId(), n.getObjectId());
       }
       this.sendEmailAndMarkNotificationAsSent(notifications, emailTemplate, recipientUser, parameters);
+    } else {
+      // Need to mark notification as sent, otherwise, it is going to be check every time the job runs.
+      OffsetDateTime sentTime = OffsetDateTime.now();
+      for (Notification notification : notifications) {
+        this.notificationService.updateSentTime(notification.getResId(), sentTime);
+        log.info("notification {} sentTime updated with value {}", notification.getResId(), sentTime);
+      }
     }
 
   }
diff --git a/DLCM-Admin/src/main/java/ch/dlcm/task/NotificationsTaskRunnable.java b/DLCM-Admin/src/main/java/ch/dlcm/task/NotificationsTaskRunnable.java
index 3e7211d0c9..474d29ab08 100644
--- a/DLCM-Admin/src/main/java/ch/dlcm/task/NotificationsTaskRunnable.java
+++ b/DLCM-Admin/src/main/java/ch/dlcm/task/NotificationsTaskRunnable.java
@@ -80,9 +80,10 @@ public abstract class NotificationsTaskRunnable extends AbstractTaskRunnable<Sch
     //Get all notifications with PENDING status, sentTime is equal to NULL and Type is equal to notificationType
     Notification search = new Notification();
     search.setNotificationType(notificationType);
-    search.setNotificationStatus(NotificationStatus.PENDING);
     NotificationSpecification notificationSpecification = new NotificationSpecification(search);
     notificationSpecification.setOnlyWithNullSentTime(true);
+    notificationSpecification.setNotificationStatusList(List.of(NotificationStatus.PENDING, NotificationStatus.NON_APPLICABLE));
+
     Pageable pageable = PageRequest.of(0, RestCollectionPage.MAX_SIZE_PAGE, Sort.by(ActionName.CREATION));
 
     return this.notificationService.findAll(notificationSpecification, pageable).toList();
diff --git a/DLCM-Model/src/main/java/ch/dlcm/message/EmailMessage.java b/DLCM-Model/src/main/java/ch/dlcm/message/EmailMessage.java
index f3cf9b020c..3fffe53c90 100644
--- a/DLCM-Model/src/main/java/ch/dlcm/message/EmailMessage.java
+++ b/DLCM-Model/src/main/java/ch/dlcm/message/EmailMessage.java
@@ -52,7 +52,12 @@ public class EmailMessage implements Serializable {
     NEW_MEMBER_ORG_UNIT_TO_APPROVE(DLCMConstants.NOTIFICATION_EMAIL_DLCM_PREFIX +  " Nouveaux membres d'unités organisationnelles à approuver /  New members of organizational units to approve"),
     NEW_REQUEST_TO_ACCESS_DATASET(DLCMConstants.NOTIFICATION_EMAIL_DLCM_PREFIX +  " Nouvelles demandes d'autorisation d'accès aux archives /  New requests to access archives archive"),
     REQUEST_TO_ACCESS_DATASET_ACCEPTED(DLCMConstants.NOTIFICATION_EMAIL_DLCM_PREFIX +  " Demande d'accès acceptée /  Request to access granted"),
-    REQUEST_TO_ACCESS_DATASET_REFUSED(DLCMConstants.NOTIFICATION_EMAIL_DLCM_PREFIX +  " Demande d'accès refusée /  Request to access refused");
+    REQUEST_TO_ACCESS_DATASET_REFUSED(DLCMConstants.NOTIFICATION_EMAIL_DLCM_PREFIX +  " Demande d'accès refusée /  Request to access refused"),
+    REQUEST_TO_CREATE_ORG_UNIT_ACCEPTED(DLCMConstants.NOTIFICATION_EMAIL_DLCM_PREFIX +  " Demande de creation d'unité organisationnelle acceptée /  Request to create an organizational unit granted"),
+    REQUEST_TO_CREATE_ORG_UNIT_REFUSED(DLCMConstants.NOTIFICATION_EMAIL_DLCM_PREFIX +  " Demande de creation d'unité organisationnelle refusée /  Request to create an organizational unit refused"),
+    REQUEST_TO_JOIN_ORG_UNIT_REFUSED(DLCMConstants.NOTIFICATION_EMAIL_DLCM_PREFIX +  " Demande d'adhésion à une unité organisationelle refusée /  Request to add members to an organizational unit refused"),
+    REQUEST_TO_JOIN_ORG_UNIT_ACCEPTED(DLCMConstants.NOTIFICATION_EMAIL_DLCM_PREFIX +  " Demande d'adhésion à une unité organisationelle acceptée /  Request to add members to an organizational unit accepted"),
+    REQUEST_TO_DISPOSE_ARCHIVE_ACCEPTED(DLCMConstants.NOTIFICATION_EMAIL_DLCM_PREFIX +  " Demande de dispose un archive acceptée /  Request to dispose of a file accepted");
 
     private final String subject;
 
diff --git a/DLCM-Model/src/main/java/ch/dlcm/specification/NotificationSpecification.java b/DLCM-Model/src/main/java/ch/dlcm/specification/NotificationSpecification.java
index 5ed961be81..2338ec7780 100644
--- a/DLCM-Model/src/main/java/ch/dlcm/specification/NotificationSpecification.java
+++ b/DLCM-Model/src/main/java/ch/dlcm/specification/NotificationSpecification.java
@@ -36,6 +36,7 @@ import jakarta.persistence.criteria.Root;
 import ch.unige.solidify.specification.SolidifySpecification;
 
 import ch.dlcm.model.notification.Notification;
+import ch.dlcm.model.notification.NotificationStatus;
 import ch.dlcm.model.notification.NotificationType;
 
 public class NotificationSpecification extends SolidifySpecification<Notification> {
@@ -49,6 +50,8 @@ public class NotificationSpecification extends SolidifySpecification<Notificatio
   private boolean onlyWithNullReadTime = false;
   private String exceptResId = null;
 
+  private List<NotificationStatus> notificationStatusList;
+
   public NotificationSpecification(Notification notification) {
     super(notification);
   }
@@ -96,6 +99,10 @@ public class NotificationSpecification extends SolidifySpecification<Notificatio
     if (this.exceptResId != null) {
       predicatesList.add(builder.notEqual(root.get("resId"), this.exceptResId));
     }
+
+    if (this.criteria.getNotificationStatus() == null && this.notificationStatusList != null) {
+      predicatesList.add(builder.and(root.get("notificationStatus").in(this.notificationStatusList)));
+    }
   }
 
   private void setNotificationTypeCriteria(Root<Notification> root, CriteriaBuilder builder, List<Predicate> predicatesList,
@@ -144,4 +151,8 @@ public class NotificationSpecification extends SolidifySpecification<Notificatio
   public void setExceptResId(String exceptResId) {
     this.exceptResId = exceptResId;
   }
+
+  public void setNotificationStatusList(List<NotificationStatus> notificationStatusList) {
+    this.notificationStatusList = notificationStatusList;
+  }
 }
diff --git a/DLCM-Model/src/main/resources/scripts/upgrade22to30.sql b/DLCM-Model/src/main/resources/scripts/upgrade22to30.sql
index 89dd0091ef..d602318ca7 100755
--- a/DLCM-Model/src/main/resources/scripts/upgrade22to30.sql
+++ b/DLCM-Model/src/main/resources/scripts/upgrade22to30.sql
@@ -373,4 +373,7 @@ SET parameters = JSON_SET(
         '$.manifestPattern', '**/*.json'
                  )
 WHERE type IN ('IIIF')
-  AND parameters IS NOT NULL;
\ No newline at end of file
+  AND parameters IS NOT NULL;
+
+
+update notification set sent_time = NOW() where sent_time is null;
\ No newline at end of file
diff --git a/DLCM-ResourceServerCommon/src/main/resources/email-templates/request_to_create_org_unit_accepted.html b/DLCM-ResourceServerCommon/src/main/resources/email-templates/request_to_create_org_unit_accepted.html
new file mode 100644
index 0000000000..0228af87cb
--- /dev/null
+++ b/DLCM-ResourceServerCommon/src/main/resources/email-templates/request_to_create_org_unit_accepted.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>Title</title>
+</head>
+<body>
+
+</body>
+</html><!DOCTYPE html>
+<html xmlns:th="http://www.thymeleaf.org">
+<head>
+    <meta http-equiv="Content-Type"
+          content="text/html; charset=UTF-8"
+    />
+</head>
+<body>
+
+<p>[English below]</p>
+
+<p>Madame, Monsieur,</p>
+
+<p>Votre demande de creation d'unité organisationelle '<a th:href="${submissionPortalHomepage} + '/preservation-space/organizational-unit/detail/' + ${orgunit.resId}"
+                                                          th:text="${orgunit.name}"
+></a>' a été acceptée.</p>
+
+<p>Avec nos cordiales salutations,
+    <br/>
+    <span th:text="${project}"> </span>
+</p>
+
+<p>--------
+    <br/>
+</p>
+
+<p>Dear Sir or Madam,</p>
+
+<p>Your request to create an organizational unit '<a th:href="${submissionPortalHomepage} + '/preservation-space/organizational-unit/detail/' + ${orgunit.resId}"
+                                          th:text="${orgunit.name}"
+></a>' has been granted.</p>
+
+<p>Kind regards,
+    <br/>
+    <span th:text="${project}"> </span>
+</p>
+
+</body>
+</html>
diff --git a/DLCM-ResourceServerCommon/src/main/resources/email-templates/request_to_create_org_unit_refused.html b/DLCM-ResourceServerCommon/src/main/resources/email-templates/request_to_create_org_unit_refused.html
new file mode 100644
index 0000000000..6daffff423
--- /dev/null
+++ b/DLCM-ResourceServerCommon/src/main/resources/email-templates/request_to_create_org_unit_refused.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>Title</title>
+</head>
+<body>
+
+</body>
+</html><!DOCTYPE html>
+<html xmlns:th="http://www.thymeleaf.org">
+<head>
+    <meta http-equiv="Content-Type"
+          content="text/html; charset=UTF-8"
+    />
+</head>
+<body>
+
+<p>[English below]</p>
+
+<p>Madame, Monsieur,</p>
+
+<p>Votre demande de creation d'unité organisationelle: '<span th:text="${orgunit.name}"></span>' a été refusée avec le motif: <span th:text="${reason}"></span>.</p>
+
+<p>Avec nos cordiales salutations,
+    <br/>
+    <span th:text="${project}"> </span>
+</p>
+
+<p>--------
+    <br/>
+</p>
+
+<p>Dear Sir or Madam,</p>
+
+<p>Your request to create an organizational unit '<span th:text="${orgunit.name}"></span>' has been refused because: <span th:text="${reason}"></span>.</p>
+
+<p>Kind regards,
+    <br/>
+    <span th:text="${project}"> </span>
+</p>
+
+</body>
+</html>
diff --git a/DLCM-ResourceServerCommon/src/main/resources/email-templates/request_to_dispose_archive_accepted.html b/DLCM-ResourceServerCommon/src/main/resources/email-templates/request_to_dispose_archive_accepted.html
new file mode 100644
index 0000000000..b344e1e6e7
--- /dev/null
+++ b/DLCM-ResourceServerCommon/src/main/resources/email-templates/request_to_dispose_archive_accepted.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html xmlns:th="http://www.thymeleaf.org">
+<head>
+    <meta http-equiv="Content-Type"
+          content="text/html; charset=UTF-8"
+    />
+</head>
+<body>
+
+<p>[English below]</p>
+
+<p>Madame, Monsieur,</p>
+<p>Votre demande de dispose de l'aip '<a th:href="${submissionPortalHomepage} + '/preservation-planning/aip/1/detail/' + ${aip.resId} +'/metadadata'"
+                                         th:text="${aip.info.name}"
+></a>' a été acceptée.</p>
+
+<p>Avec nos cordiales salutations,
+    <br/>
+    <span th:text="${project}"> </span>
+</p>
+
+<p>--------
+    <br/>
+</p>
+
+<p>Dear Sir or Madam,</p>
+
+<p>Your request to dispose d'aip '<a th:href="${submissionPortalHomepage} + '/preservation-planning/aip/1/detail/' + ${aip.resId} +'/metadadata'"
+                                     th:text="${aip.info.name}"
+></a>' has been granted.</p>
+
+<p>Kind regards,
+    <br/>
+    <span th:text="${project}"> </span>
+</p>
+
+</body>
+</html>
diff --git a/DLCM-ResourceServerCommon/src/main/resources/email-templates/request_to_join_org_unit_accepted.html b/DLCM-ResourceServerCommon/src/main/resources/email-templates/request_to_join_org_unit_accepted.html
new file mode 100644
index 0000000000..a1c5e98357
--- /dev/null
+++ b/DLCM-ResourceServerCommon/src/main/resources/email-templates/request_to_join_org_unit_accepted.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>Title</title>
+</head>
+<body>
+
+</body>
+</html><!DOCTYPE html>
+<html xmlns:th="http://www.thymeleaf.org">
+<head>
+    <meta http-equiv="Content-Type"
+          content="text/html; charset=UTF-8"
+    />
+</head>
+<body>
+
+<p>[English below]</p>
+
+<p>Madame, Monsieur,</p>
+
+<p>Votre demande d'être membre de l'unité organisationelle '<a th:href="${submissionPortalHomepage} + '/preservation-space/organizational-unit/detail/' + ${orgunit.resId}"
+                                                          th:text="${orgunit.name}"
+></a>' a été acceptée.</p>
+
+<p>Avec nos cordiales salutations,
+    <br/>
+    <span th:text="${project}"> </span>
+</p>
+
+<p>--------
+    <br/>
+</p>
+
+<p>Dear Sir or Madam,</p>
+
+<p>Your request of being member of the organizational unit '<a th:href="${submissionPortalHomepage} + '/preservation-space/organizational-unit/detail/' + ${orgunit.resId}"
+                                                     th:text="${orgunit.name}"
+></a>' has been granted.</p>
+
+<p>Kind regards,
+    <br/>
+    <span th:text="${project}"> </span>
+</p>
+
+</body>
+</html>
diff --git a/DLCM-ResourceServerCommon/src/main/resources/email-templates/request_to_join_org_unit_refused.html b/DLCM-ResourceServerCommon/src/main/resources/email-templates/request_to_join_org_unit_refused.html
new file mode 100644
index 0000000000..b9992c6d73
--- /dev/null
+++ b/DLCM-ResourceServerCommon/src/main/resources/email-templates/request_to_join_org_unit_refused.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>Title</title>
+</head>
+<body>
+
+</body>
+</html><!DOCTYPE html>
+<html xmlns:th="http://www.thymeleaf.org">
+<head>
+    <meta http-equiv="Content-Type"
+          content="text/html; charset=UTF-8"
+    />
+</head>
+<body>
+
+<p>[English below]</p>
+
+<p>Madame, Monsieur,</p>
+
+<p>Votre demande d'être membre de l'unité organisationelle: '<a th:href="${submissionPortalHomepage} + '/preservation-space/organizational-unit/detail/' + ${orgunit.resId}"
+                                                                th:text="${orgunit.name}"
+></a>' a été refusée avec le motif: <span th:text="${reason}"></span>.</p>
+
+
+<p>Avec nos cordiales salutations,
+    <br/>
+    <span th:text="${project}"> </span>
+</p>
+
+<p>--------
+    <br/>
+</p>
+
+<p>Dear Sir or Madam,</p>
+
+<p>Your request to be member of the organizational unit '<a th:href="${submissionPortalHomepage} + '/preservation-space/organizational-unit/detail/' + ${orgunit.resId}"
+                                                           th:text="${orgunit.name}"
+></a>' has been refused because: <span th:text="${reason}"></span>.</p>
+
+
+<p>Kind regards,
+    <br/>
+    <span th:text="${project}"> </span>
+</p>
+
+</body>
+</html>
-- 
GitLab


From eee0b9042b2e03bed2755683efa55b2fcf268996 Mon Sep 17 00:00:00 2001
From: Alicia de Dios Fuente <Alicia.DeDiosFuente@unige.ch>
Date: Mon, 17 Feb 2025 17:33:15 +0100
Subject: [PATCH 2/3] feat: unable to change notification status once
 approved/rejected

---
 .../ch/dlcm/business/NotificationService.java |  3 ++
 .../test/admin/NotificationAsAdminIT.java     | 11 ++--
 .../dlcm/test/admin/NotificationAsUserIT.java | 53 ++++++++++++++-----
 .../dlcm/model/notification/Notification.java |  6 +++
 4 files changed, 56 insertions(+), 17 deletions(-)

diff --git a/DLCM-Admin/src/main/java/ch/dlcm/business/NotificationService.java b/DLCM-Admin/src/main/java/ch/dlcm/business/NotificationService.java
index 32f9a00a9a..7ef5d2b79b 100644
--- a/DLCM-Admin/src/main/java/ch/dlcm/business/NotificationService.java
+++ b/DLCM-Admin/src/main/java/ch/dlcm/business/NotificationService.java
@@ -382,6 +382,9 @@ public class NotificationService extends ResourceService<Notification> {
               NotificationStatus.REFUSED.equals(newStatus)) {
         throw new SolidifyUnmodifiableException("Notification of category INFO cannot be refused");
       }
+      if (notification.getNotificationStatus().equals(NotificationStatus.APPROVED) || notification.getNotificationStatus().equals(NotificationStatus.REFUSED)) {
+        throw new SolidifyUnmodifiableException("Notification already approved or refused, cannot be changed");
+      }
       notification.setNotificationStatus(newStatus);
       notification.setResponseMessage(responseMessage);
       this.save(notification);
diff --git a/DLCM-IntegrationTests/src/test/java/ch/dlcm/test/admin/NotificationAsAdminIT.java b/DLCM-IntegrationTests/src/test/java/ch/dlcm/test/admin/NotificationAsAdminIT.java
index 27433d5f1f..73c8bffcdf 100644
--- a/DLCM-IntegrationTests/src/test/java/ch/dlcm/test/admin/NotificationAsAdminIT.java
+++ b/DLCM-IntegrationTests/src/test/java/ch/dlcm/test/admin/NotificationAsAdminIT.java
@@ -86,10 +86,13 @@ class NotificationAsAdminIT extends AbstractNotificationIT {
     remoteNotification = this.notificationClientService.findOne(notificationId);
     assertEquals(NotificationStatus.APPROVED, remoteNotification.getNotificationStatus(), "Notification status should be approved");
 
-    this.notificationClientService.setRefusedStatus(notificationId, REFUSAL_REASON);
-    remoteNotification = this.notificationClientService.findOne(notificationId);
-    assertEquals(NotificationStatus.REFUSED, remoteNotification.getNotificationStatus(), "Notification status should be refused");
-    assertEquals(REFUSAL_REASON, remoteNotification.getResponseMessage());
+    final Notification localNotification2 = this.createLocalNotification(NotificationType.JOIN_ORGUNIT_REQUEST, notifiedOrgUnit);
+
+    Notification remoteNotification2 = this.createRemoteNotification(localNotification2);
+    this.notificationClientService.setRefusedStatus(remoteNotification2.getResId(), REFUSAL_REASON);
+    remoteNotification2 = this.notificationClientService.findOne(remoteNotification2.getResId());
+    assertEquals(NotificationStatus.REFUSED, remoteNotification2.getNotificationStatus(), "Notification status should be refused");
+    assertEquals(REFUSAL_REASON, remoteNotification2.getResponseMessage());
   }
 
   @Test
diff --git a/DLCM-IntegrationTests/src/test/java/ch/dlcm/test/admin/NotificationAsUserIT.java b/DLCM-IntegrationTests/src/test/java/ch/dlcm/test/admin/NotificationAsUserIT.java
index 8ffdd4819c..e4daa61d4a 100644
--- a/DLCM-IntegrationTests/src/test/java/ch/dlcm/test/admin/NotificationAsUserIT.java
+++ b/DLCM-IntegrationTests/src/test/java/ch/dlcm/test/admin/NotificationAsUserIT.java
@@ -112,22 +112,25 @@ class NotificationAsUserIT extends AbstractNotificationIT {
     remoteNotification = this.notificationClientService.getInboxNotification(remoteNotification.getResId());
     assertEquals(NotificationStatus.APPROVED, remoteNotification.getNotificationStatus(), "Notification status should be APPROVED");
 
-    this.notificationClientService.setRefusedStatus(remoteNotification.getResId(), REFUSAL_REASON);
-    remoteNotification = this.notificationClientService.getInboxNotification(remoteNotification.getResId());
-    assertEquals(NotificationStatus.REFUSED, remoteNotification.getNotificationStatus(), "Notification status should be REFUSED");
-    assertEquals(REFUSAL_REASON, remoteNotification.getResponseMessage());
+    final Notification localNotification2 = this.createLocalNotification(NotificationType.JOIN_ORGUNIT_REQUEST, organizationalUnit);
 
-    this.notificationClientService.setPendingStatus(remoteNotification.getResId());
-    remoteNotification = this.notificationClientService.getInboxNotification(remoteNotification.getResId());
-    assertEquals(NotificationStatus.PENDING, remoteNotification.getNotificationStatus(), "Notification status should be PENDING");
+    Notification remoteNotification2 = this.createRemoteNotification(localNotification2);
+    this.notificationClientService.setPendingStatus(remoteNotification2.getResId());
+    remoteNotification2 = this.notificationClientService.getInboxNotification(remoteNotification2.getResId());
+    assertEquals(NotificationStatus.PENDING, remoteNotification2.getNotificationStatus(), "Notification status should be PENDING");
 
-    this.notificationClientService.setReadMark(remoteNotification.getResId());
-    remoteNotification = this.notificationClientService.getInboxNotification(remoteNotification.getResId());
-    assertEquals(NotificationMark.READ, remoteNotification.getNotificationMark(), "Notification mark should be READ");
+    this.notificationClientService.setRefusedStatus(remoteNotification2.getResId(), REFUSAL_REASON);
+    remoteNotification2 = this.notificationClientService.getInboxNotification(remoteNotification2.getResId());
+    assertEquals(NotificationStatus.REFUSED, remoteNotification2.getNotificationStatus(), "Notification status should be REFUSED");
+    assertEquals(REFUSAL_REASON, remoteNotification2.getResponseMessage());
 
-    this.notificationClientService.setUnreadMark(remoteNotification.getResId());
-    remoteNotification = this.notificationClientService.getInboxNotification(remoteNotification.getResId());
-    assertEquals(NotificationMark.UNREAD, remoteNotification.getNotificationMark(), "Notification mark should be UNREAD");
+    this.notificationClientService.setReadMark(remoteNotification2.getResId());
+    remoteNotification2 = this.notificationClientService.getInboxNotification(remoteNotification2.getResId());
+    assertEquals(NotificationMark.READ, remoteNotification2.getNotificationMark(), "Notification mark should be READ");
+
+    this.notificationClientService.setUnreadMark(remoteNotification2.getResId());
+    remoteNotification2 = this.notificationClientService.getInboxNotification(remoteNotification2.getResId());
+    assertEquals(NotificationMark.UNREAD, remoteNotification2.getNotificationMark(), "Notification mark should be UNREAD");
   }
 
   @Test
@@ -156,4 +159,28 @@ class NotificationAsUserIT extends AbstractNotificationIT {
     this.checkUnableToCreateMultiplesNotificationsForSameUser(NotificationType.JOIN_ORGUNIT_REQUEST);
   }
 
+  @Test
+  void cannotChangeNotificationStatusAlreadyAcceptedOrRefused() {
+    final Person currentPerson = this.personITService.getLinkedPersonToCurrentUser();
+    this.restClientTool.sudoAdmin();
+    final OrganizationalUnit organizationalUnit = this.orgUnitITService.createTemporaryRemoteOrgUnit(
+            "Org unit for testing changes in notification status ", new ArrayList<>());
+    this.orgUnitITService.enforcePersonHasRoleInOrgUnit(organizationalUnit, currentPerson, Role.MANAGER_ID);
+    this.restClientTool.exitSudo();
+
+    final Notification localNotification = this.createLocalNotification(NotificationType.JOIN_ORGUNIT_REQUEST, organizationalUnit);
+
+    Notification remoteNotification = this.createRemoteNotification(localNotification);
+    assertEquals(NotificationStatus.PENDING, remoteNotification.getNotificationStatus(), "Initial notification status should be PENDING");
+    assertEquals(NotificationMark.UNREAD, remoteNotification.getNotificationMark(), "Initial notification should be UNREAD");
+
+    this.notificationClientService.setProcessedStatus(remoteNotification.getResId());
+    remoteNotification = this.notificationClientService.getInboxNotification(remoteNotification.getResId());
+    assertEquals(NotificationStatus.APPROVED, remoteNotification.getNotificationStatus(), "Notification status should be APPROVED");
+
+    remoteNotification.setNotificationStatus(NotificationStatus.REFUSED);
+    final String notificationId = remoteNotification.getResId();
+    final Notification updatedNotification = remoteNotification;
+    assertThrows(HttpClientErrorException.Forbidden.class, () ->  this.notificationClientService.update(notificationId, updatedNotification));
+  }
 }
diff --git a/DLCM-Model/src/main/java/ch/dlcm/model/notification/Notification.java b/DLCM-Model/src/main/java/ch/dlcm/model/notification/Notification.java
index 24bc3ecf39..9ea60235b1 100644
--- a/DLCM-Model/src/main/java/ch/dlcm/model/notification/Notification.java
+++ b/DLCM-Model/src/main/java/ch/dlcm/model/notification/Notification.java
@@ -285,4 +285,10 @@ public class Notification extends ResourceNormalized implements ResourceFileInte
   public void setResourceFile(ResourceFile resourceFile) {
     this.setSignedDuaFile((SignedDuaFile) resourceFile);
   }
+
+  @Override
+  @JsonIgnore
+  public boolean isModifiable() {
+    return (this.getNotificationStatus() != NotificationStatus.REFUSED && this.getNotificationStatus() != NotificationStatus.APPROVED);
+  }
 }
-- 
GitLab


From 73065d66ef470c929df646f807ee048c2937279a Mon Sep 17 00:00:00 2001
From: Alicia de Dios Fuente <Alicia.DeDiosFuente@unige.ch>
Date: Wed, 19 Feb 2025 15:21:22 +0100
Subject: [PATCH 3/3] fix: merge review

---
 .../src/main/java/ch/dlcm/business/NotificationService.java   | 4 ++--
 .../main/java/ch/dlcm/service/NotificationsEmailService.java  | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/DLCM-Admin/src/main/java/ch/dlcm/business/NotificationService.java b/DLCM-Admin/src/main/java/ch/dlcm/business/NotificationService.java
index 7ef5d2b79b..e659586a1f 100644
--- a/DLCM-Admin/src/main/java/ch/dlcm/business/NotificationService.java
+++ b/DLCM-Admin/src/main/java/ch/dlcm/business/NotificationService.java
@@ -284,7 +284,7 @@ public class NotificationService extends ResourceService<Notification> {
   @Override
   public Notification save(Notification item) {
 
-    // Send an email to emitter for certain types of notification
+    // Email emitter for certain types of notification
     this.sendEmailToEmitter(item);
 
     // Status is non-applicable if notification category is info
@@ -307,7 +307,7 @@ public class NotificationService extends ResourceService<Notification> {
               || notification.getNotificationStatus().equals(NotificationStatus.REFUSED))) {
       EmailMessage.EmailTemplate emailTemplate = this.getEmailTemplate(notification.getNotificationType(), notification.getNotificationStatus());
       if (emailTemplate != null) {
-        Map<String, Object> parameters = new HashMap();
+        Map<String, Object> parameters = new HashMap<>();
         if (notification.getNotificationType().equals(NotificationType.JOIN_ORGUNIT_REQUEST)) {
           parameters.put("objectId", notification.getNotifiedOrgUnit().getResId());
         } else {
diff --git a/DLCM-Admin/src/main/java/ch/dlcm/service/NotificationsEmailService.java b/DLCM-Admin/src/main/java/ch/dlcm/service/NotificationsEmailService.java
index c34f60da74..a4ee75d21b 100644
--- a/DLCM-Admin/src/main/java/ch/dlcm/service/NotificationsEmailService.java
+++ b/DLCM-Admin/src/main/java/ch/dlcm/service/NotificationsEmailService.java
@@ -187,7 +187,7 @@ public class NotificationsEmailService extends DLCMService {
       }
       this.sendEmailAndMarkNotificationAsSent(notifications, emailTemplate, recipientUser, parameters);
     } else {
-      // Need to mark notification as sent, otherwise, it is going to be check every time the job runs.
+      // Need to mark notification as sent, otherwise, it is going to be checked every time the job runs.
       OffsetDateTime sentTime = OffsetDateTime.now();
       for (Notification notification : notifications) {
         this.notificationService.updateSentTime(notification.getResId(), sentTime);
-- 
GitLab