ActionNotificationsBase.java

/*
 * Copyright 2024 Global Crop Diversity Trust
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.gringlobal.notification;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;
import org.gringlobal.model.AbstractAction;
import org.gringlobal.model.Cooperator;
import org.gringlobal.model.SysUser;
import org.gringlobal.model.notification.NotificationSchedule;
import org.gringlobal.service.ActionService;
import org.gringlobal.service.ActionService.ActionRequest;
import org.gringlobal.service.ActionService.ActionScheduleFilter;
import org.gringlobal.service.filter.ActionFilter;
import org.gringlobal.service.AppResourceService;
import org.gringlobal.service.EMailService;
import org.gringlobal.service.TemplatingService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.i18n.LocaleContextHolder;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public abstract class ActionNotificationsBase<T extends AbstractAction<T>, F extends ActionFilter<F, T>, R extends ActionRequest, ASF extends ActionScheduleFilter<ASF, T>> {

	@Autowired
	protected AppResourceService appResourceService;

	@Autowired
	protected EMailService emailService;

	@Autowired
	protected TemplatingService templatingService;

	@Value("${frontend.url}")
	protected String frontendUrl;

	protected abstract String getFrontendUrl();

	protected abstract String getCodeValueGroup();

	protected abstract ActionService<T, F, R, ASF> getService();


	protected void generateNotification(NotificationSchedule schedule, List<SysUser> recipients, ASF overviewFilter) throws Exception {
		log.info("Generating notifications");

		// Recipients emails
		var sysUserRecipients = recipients.stream()
			.map(SysUser::getCooperator) // To cooperator
			.filter(Objects::nonNull)
			.peek(cooperator -> log.debug("Notifiying {}", cooperator))
			.map(Cooperator::getEmail) // To email
			.filter(StringUtils::isNotBlank)
			.collect(Collectors.toSet());

		if (sysUserRecipients.isEmpty()) return;

		var overview = getService().actionScheduleOverview(overviewFilter);

		ActionTemplateData actionTemplateData = new ActionTemplateData();
		actionTemplateData.scheduled = overview.scheduled.entrySet();
		actionTemplateData.added = overview.added.entrySet();
		actionTemplateData.created = overview.created.entrySet();
		actionTemplateData.completed = overview.completed.entrySet();
		actionTemplateData.canceled = overview.canceled.entrySet();
		actionTemplateData.inProgress = overview.inProgress.entrySet();
		actionTemplateData.overdue = overview.overdue.entrySet();

		// Count sum for each action category
		actionTemplateData.scheduledSum = overview.scheduled.values().stream().map(number -> (long) number).reduce(0L, Long::sum);
		actionTemplateData.addedSum = overview.added.values().stream().map(number -> (long) number).reduce(0L, Long::sum);
		actionTemplateData.createdSum = overview.created.values().stream().map(number -> (long) number).reduce(0L, Long::sum);
		actionTemplateData.completedSum = overview.completed.values().stream().map(number -> (long) number).reduce(0L, Long::sum);
		actionTemplateData.canceledSum = overview.canceled.values().stream().map(number -> (long) number).reduce(0L, Long::sum);
		actionTemplateData.inProgressSum = overview.inProgress.values().stream().map(number -> (long) number).reduce(0L, Long::sum);
		actionTemplateData.overdueSum = overview.overdue.values().stream().map(number -> (long) number).reduce(0L, Long::sum);

		var count = actionTemplateData.scheduledSum + actionTemplateData.addedSum + actionTemplateData.createdSum + actionTemplateData.completedSum + actionTemplateData.canceledSum + actionTemplateData.inProgressSum + actionTemplateData.overdueSum;
		if (count == 0) {
			log.info("Nothing interesting to send! Skipping.");
			return;
		}

		Map<String, Object> templateParams = new HashMap<>();
		templateParams.put("data", actionTemplateData);
		templateParams.put("fromInstant", overviewFilter.fromInclusive);
		templateParams.put("toInstant", overviewFilter.toExclusive);
		templateParams.put("actionOverviewURL", getFrontendUrl());
		templateParams.put("title", schedule.getTitle());
		templateParams.put("actionCodeGroup", getCodeValueGroup());

		var resource = appResourceService.getResource(AppResourceService.APP_NAME_GGCE, "notification/action-schedule.mustache", LocaleContextHolder.getLocale());
		String template;
		if (resource != null) {
			template = resource.getDisplayMember();
		} else {
			log.info("AppResource 'notification/action-schedule.mustache' not found, using bundled template");
			template = Files.readString(Path.of(getClass().getResource("/notification/action-schedule.mustache").getPath()));
		}

		var messageBody = templatingService.fillTemplate(template, templateParams);

		for (var emailTo : sysUserRecipients) {
			emailService.sendMail(schedule.getTitle(), messageBody, emailTo);
		}
	}


	@Getter
	@Setter
	private static class ActionTemplateData {
		public Set<Map.Entry<String, Number>> scheduled;
		public Set<Map.Entry<String, Number>> added;
		public Set<Map.Entry<String, Number>> created;
		public Set<Map.Entry<String, Number>> completed;
		public Set<Map.Entry<String, Number>> canceled;
		public Set<Map.Entry<String, Number>> inProgress;
		public Set<Map.Entry<String, Number>> overdue;

		public Long scheduledSum;
		public Long addedSum;
		public Long createdSum;
		public Long completedSum;
		public Long canceledSum;
		public Long inProgressSum;
		public Long overdueSum;
	}
}