From 8146a7399c2ed936f7d353f0f4777bb4e5006d76 Mon Sep 17 00:00:00 2001 From: Adonay Date: Thu, 20 May 2021 16:41:32 -0500 Subject: [PATCH] FINERACT-1273: Triggered emails Basically send an email when a client does an action: for now when a loan is approved, rejected and when a repayment is done. More triggers should be added in the future --- .../campaigns/email/domain/EmailCampaign.java | 12 +- .../email/domain/EmailCampaignRepository.java | 9 +- .../email/domain/EmailCampaignType.java | 10 +- .../service/EmailCampaignDomainService.java | 21 +++ .../EmailCampaignDomainServiceImpl.java | 151 ++++++++++++++++++ ...mpaignWritePlatformCommandHandlerImpl.java | 78 +++++---- .../EmailCampaignWritePlatformService.java | 5 + .../EmailMessageJobEmailServiceImpl.java | 64 +++----- ...AddressCommandFromApiJsonDeserializer.java | 7 +- .../V367__reports_for_email_campaigns.sql | 26 +++ ...> V370__message_gateway_hook_template.sql} | 0 .../integrationtests/client/ReportsTest.java | 2 +- 12 files changed, 305 insertions(+), 80 deletions(-) create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignDomainService.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignDomainServiceImpl.java create mode 100644 fineract-provider/src/main/resources/sql/migrations/core_db/V367__reports_for_email_campaigns.sql rename fineract-provider/src/main/resources/sql/migrations/core_db/{V367__message_gateway_hook_template.sql => V370__message_gateway_hook_template.sql} (100%) diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/domain/EmailCampaign.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/domain/EmailCampaign.java index 272f2190c..72ac2264b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/domain/EmailCampaign.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/domain/EmailCampaign.java @@ -75,11 +75,11 @@ public class EmailCampaign extends AbstractPersistableCustom { @Column(name = "email_message", nullable = false) private String emailMessage; - @Column(name = "email_attachment_file_format", nullable = false) + @Column(name = "email_attachment_file_format") private String emailAttachmentFileFormat; @ManyToOne - @JoinColumn(name = "stretchy_report_id", nullable = false) + @JoinColumn(name = "stretchy_report_id") private Report stretchyReport; @Column(name = "stretchy_report_param_map", nullable = true) @@ -109,18 +109,18 @@ public class EmailCampaign extends AbstractPersistableCustom { @JoinColumn(name = "approvedon_userid", nullable = true) private AppUser approvedBy; - @Column(name = "recurrence", nullable = false) + @Column(name = "recurrence") private String recurrence; - @Column(name = "next_trigger_date", nullable = false) + @Column(name = "next_trigger_date") @Temporal(TemporalType.TIMESTAMP) private Date nextTriggerDate; - @Column(name = "last_trigger_date", nullable = false) + @Column(name = "last_trigger_date") @Temporal(TemporalType.TIMESTAMP) private Date lastTriggerDate; - @Column(name = "recurrence_start_date", nullable = false) + @Column(name = "recurrence_start_date") @Temporal(TemporalType.TIMESTAMP) private Date recurrenceStartDate; diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/domain/EmailCampaignRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/domain/EmailCampaignRepository.java index 291698b91..8a0eec294 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/domain/EmailCampaignRepository.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/domain/EmailCampaignRepository.java @@ -18,7 +18,14 @@ */ package org.apache.fineract.infrastructure.campaigns.email.domain; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; -public interface EmailCampaignRepository extends JpaRepository, JpaSpecificationExecutor {} +public interface EmailCampaignRepository extends JpaRepository, JpaSpecificationExecutor { + + @Query("SELECT campaign FROM EmailCampaign campaign WHERE campaign.paramValue LIKE :reportPattern AND campaign.campaignType=:type AND campaign.status=300") + List findActiveEmailCampaigns(@Param("reportPattern") String reportPattern, @Param("type") Integer type); +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/domain/EmailCampaignType.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/domain/EmailCampaignType.java index 95c36ce00..79059eaad 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/domain/EmailCampaignType.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/domain/EmailCampaignType.java @@ -20,7 +20,8 @@ package org.apache.fineract.infrastructure.campaigns.email.domain; public enum EmailCampaignType { - DIRECT(1, "emailCampaignStatusType.direct"), SCHEDULE(2, "emailCampaignStatusType.schedule"); + DIRECT(1, "emailCampaignStatusType.direct"), SCHEDULE(2, "emailCampaignStatusType.schedule"), TRIGGERED(3, + "emailCampaignStatusType.triggered"); private final Integer value; private final String code; @@ -47,6 +48,9 @@ public enum EmailCampaignType { case 2: type = SCHEDULE; break; + case 3: + type = TRIGGERED; + break; } return type; } @@ -58,4 +62,8 @@ public enum EmailCampaignType { public boolean isSchedule() { return this.value.equals(EmailCampaignType.SCHEDULE.getValue()); } + + public boolean isTriggered() { + return this.value.equals(EmailCampaignType.TRIGGERED.getValue()); + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignDomainService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignDomainService.java new file mode 100644 index 000000000..c02d697a9 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignDomainService.java @@ -0,0 +1,21 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.fineract.infrastructure.campaigns.email.service; + +public interface EmailCampaignDomainService {} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignDomainServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignDomainServiceImpl.java new file mode 100644 index 000000000..31342a90b --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignDomainServiceImpl.java @@ -0,0 +1,151 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.fineract.infrastructure.campaigns.email.service; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.annotation.PostConstruct; +import org.apache.fineract.infrastructure.campaigns.email.domain.EmailCampaign; +import org.apache.fineract.infrastructure.campaigns.email.domain.EmailCampaignRepository; +import org.apache.fineract.infrastructure.campaigns.sms.constants.SmsCampaignTriggerType; +import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants; +import org.apache.fineract.portfolio.common.service.BusinessEventListener; +import org.apache.fineract.portfolio.common.service.BusinessEventNotifierService; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.type.TypeReference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class EmailCampaignDomainServiceImpl implements EmailCampaignDomainService { + + private static final Logger LOG = LoggerFactory.getLogger(EmailCampaignDomainServiceImpl.class); + private final BusinessEventNotifierService businessEventNotifierService; + private final EmailCampaignWritePlatformService emailCampaignWritePlatformService; + private final EmailCampaignRepository emailCampaignRepository; + + @Autowired + public EmailCampaignDomainServiceImpl(BusinessEventNotifierService businessEventNotifierService, + EmailCampaignWritePlatformService emailCampaignWritePlatformService, EmailCampaignRepository emailCampaignRepository) { + this.businessEventNotifierService = businessEventNotifierService; + this.emailCampaignWritePlatformService = emailCampaignWritePlatformService; + this.emailCampaignRepository = emailCampaignRepository; + } + + @PostConstruct + public void addListeners() { + this.businessEventNotifierService.addBusinessEventPostListeners(BusinessEventNotificationConstants.BusinessEvents.LOAN_APPROVED, + new EmailCampaignDomainServiceImpl.SendEmailOnLoanApproved()); + this.businessEventNotifierService.addBusinessEventPostListeners(BusinessEventNotificationConstants.BusinessEvents.LOAN_REJECTED, + new EmailCampaignDomainServiceImpl.SendEmailOnLoanRejected()); + this.businessEventNotifierService.addBusinessEventPostListeners( + BusinessEventNotificationConstants.BusinessEvents.LOAN_MAKE_REPAYMENT, + new EmailCampaignDomainServiceImpl.SendEmailOnLoanRepayment()); + } + + private class SendEmailOnLoanRepayment extends EmailBusinessEventAdapter { + + @Override + public void businessEventWasExecuted(Map businessEventEntity) { + Object entity = businessEventEntity.get(BusinessEventNotificationConstants.BusinessEntity.LOAN_TRANSACTION); + if (entity instanceof LoanTransaction) { + LoanTransaction loanTransaction = (LoanTransaction) entity; + try { + notifyLoanOwner(loanTransaction, "Loan Repayment"); + } catch (IOException e) { + LOG.error("Exception when trying to send triggered email: {}", e.getMessage()); + } + } + } + } + + private class SendEmailOnLoanRejected extends EmailBusinessEventAdapter { + + @Override + public void businessEventWasExecuted(Map businessEventEntity) { + Object entity = businessEventEntity.get(BusinessEventNotificationConstants.BusinessEntity.LOAN); + if (entity instanceof Loan) { + Loan loan = (Loan) entity; + try { + notifyLoanOwner(loan, "Loan Rejected"); + } catch (IOException e) { + LOG.error("Exception when trying to send triggered email: {}", e.getMessage()); + } + } + } + } + + private class SendEmailOnLoanApproved extends EmailBusinessEventAdapter { + + @Override + public void businessEventWasExecuted(Map businessEventEntity) { + Object entity = businessEventEntity.get(BusinessEventNotificationConstants.BusinessEntity.LOAN); + if (entity instanceof Loan) { + Loan loan = (Loan) entity; + try { + notifyLoanOwner(loan, "Loan Approved"); + } catch (IOException e) { + LOG.error("Exception when trying to send triggered email: {}", e.getMessage()); + } + } + } + } + + private void notifyLoanOwner(LoanTransaction loanTransaction, String paramValue) throws IOException { + List campaigns = this.retrieveEmailCampaigns(paramValue); + for (EmailCampaign emailCampaign : campaigns) { + HashMap campaignParams = new ObjectMapper().readValue(emailCampaign.getParamValue(), + new TypeReference>() {}); + campaignParams.put("loanId", loanTransaction.getLoan().getId().toString()); + campaignParams.put("loanTransactionId", loanTransaction.getId().toString()); + this.emailCampaignWritePlatformService.insertDirectCampaignIntoEmailOutboundTable(loanTransaction.getLoan(), emailCampaign, + campaignParams); + } + } + + private void notifyLoanOwner(Loan loan, String paramValue) throws IOException { + List campaigns = this.retrieveEmailCampaigns(paramValue); + for (EmailCampaign emailCampaign : campaigns) { + HashMap campaignParams = new ObjectMapper().readValue(emailCampaign.getParamValue(), + new TypeReference>() {}); + campaignParams.put("loanId", loan.getId().toString()); + this.emailCampaignWritePlatformService.insertDirectCampaignIntoEmailOutboundTable(loan, emailCampaign, campaignParams); + } + } + + private abstract static class EmailBusinessEventAdapter implements BusinessEventListener { + + @Override + public void businessEventToBeExecuted(Map businessEventEntity) { + // Nothing to do + } + } + + private List retrieveEmailCampaigns(String paramValue) { + List emailCampaigns = emailCampaignRepository.findActiveEmailCampaigns("%" + paramValue + "%", + SmsCampaignTriggerType.TRIGGERED.getValue()); + return emailCampaigns; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignWritePlatformCommandHandlerImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignWritePlatformCommandHandlerImpl.java index 5ba082d66..543c23bb6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignWritePlatformCommandHandlerImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignWritePlatformCommandHandlerImpl.java @@ -158,20 +158,24 @@ public class EmailCampaignWritePlatformCommandHandlerImpl implements EmailCampai final Long reportId = command.longValueOfParameterNamed(EmailCampaignValidator.stretchyReportId); - final Report report = this.reportRepository.findById(reportId).orElseThrow(() -> new ReportNotFoundException(reportId)); + Report report = null; + Map stretchyReportParams = null; + if (reportId != null) { + report = this.reportRepository.findById(reportId).orElseThrow(() -> new ReportNotFoundException(reportId)); + final Set reportParameterUsages = report.getReportParameterUsages(); + stretchyReportParams = new HashMap<>(); - // find all report parameters and store them as json string - final Set reportParameterUsages = report.getReportParameterUsages(); - final Map stretchyReportParams = new HashMap<>(); - - if (reportParameterUsages != null && !reportParameterUsages.isEmpty()) { - for (final ReportParameterUsage reportParameterUsage : reportParameterUsages) { - stretchyReportParams.put(reportParameterUsage.getReportParameterName(), ""); + if (reportParameterUsages != null && !reportParameterUsages.isEmpty()) { + for (final ReportParameterUsage reportParameterUsage : reportParameterUsages) { + stretchyReportParams.put(reportParameterUsage.getReportParameterName(), ""); + } } } EmailCampaign emailCampaign = EmailCampaign.instance(currentUser, businessRule, report, command); - emailCampaign.setStretchyReportParamMap(new Gson().toJson(stretchyReportParams)); + if (stretchyReportParams != null) { + emailCampaign.setStretchyReportParamMap(new Gson().toJson(stretchyReportParams)); + } this.emailCampaignRepository.save(emailCampaign); @@ -241,6 +245,32 @@ public class EmailCampaignWritePlatformCommandHandlerImpl implements EmailCampai } + @Override + public void insertDirectCampaignIntoEmailOutboundTable(final Loan loan, final EmailCampaign emailCampaign, + HashMap campaignParams) { + try { + List> runReportObject = this.getRunReportByServiceImpl(campaignParams.get("reportName"), + campaignParams); + + if (runReportObject != null) { + for (HashMap entry : runReportObject) { + String message = this.compileEmailTemplate(emailCampaign.getEmailMessage(), emailCampaign.getCampaignName(), entry); + Client client = loan.getClient(); + String emailAddress = client.emailAddress(); + + if (emailAddress != null && isValidEmail(emailAddress)) { + EmailMessage emailMessage = EmailMessage.pendingEmail(null, client, null, emailCampaign, + emailCampaign.getEmailSubject(), message, emailAddress, emailCampaign.getCampaignName()); + this.emailMessageRepository.save(emailMessage); + } + } + } + } catch (final IOException e) { + // TODO throw something here + } + + } + private void insertDirectCampaignIntoEmailOutboundTable(final String emailParams, final String emailSubject, final String messageTemplate, final String campaignName, final Long campaignId) { try { @@ -589,8 +619,11 @@ public class EmailCampaignWritePlatformCommandHandlerImpl implements EmailCampai final EmailCampaign emailCampaign = this.emailCampaignRepository.findById(emailMessage.getEmailCampaign().getId()) .orElse(null); // - final ScheduledEmailAttachmentFileFormat emailAttachmentFileFormat = ScheduledEmailAttachmentFileFormat - .instance(emailCampaign.getEmailAttachmentFileFormat()); + ScheduledEmailAttachmentFileFormat emailAttachmentFileFormat = null; + if (emailCampaign.getEmailAttachmentFileFormat() != null) { + emailAttachmentFileFormat = ScheduledEmailAttachmentFileFormat + .instance(emailCampaign.getEmailAttachmentFileFormat()); + } final List attachmentList = new ArrayList<>(); @@ -690,29 +723,16 @@ public class EmailCampaignWritePlatformCommandHandlerImpl implements EmailCampai final EmailMessageWithAttachmentData emailMessageWithAttachmentData = EmailMessageWithAttachmentData.createNew( emailMessage.getEmailAddress(), emailMessage.getMessage(), emailMessage.getEmailSubject(), attachmentList); - - if (!attachmentList.isEmpty() && attachmentList.size() > 0) { // only - // send - // email - // message - // if - // there - // is - // an - // attachment - // to - // it + try { this.emailMessageJobEmailService.sendEmailWithAttachment(emailMessageWithAttachmentData); emailMessage.setStatusType(EmailMessageStatusType.SENT.getValue()); this.emailMessageRepository.save(emailMessage); - } else { - emailMessage.updateErrorMessage(errorLog.toString()); - + } catch (Exception e) { + emailMessage.updateErrorMessage(e.getMessage()); emailMessage.setStatusType(EmailMessageStatusType.FAILED.getValue()); - this.emailMessageRepository.save(emailMessage); } } @@ -734,7 +754,9 @@ public class EmailCampaignWritePlatformCommandHandlerImpl implements EmailCampai */ private File generateAttachments(final EmailCampaign emailCampaign, final ScheduledEmailAttachmentFileFormat emailAttachmentFileFormat, final Map reportParams, final String reportName, final StringBuilder errorLog) { - + if (reportName == null) { + return null; + } try { final ByteArrayOutputStream byteArrayOutputStream = this.readReportingService.generatePentahoReportAsOutputStream(reportName, emailAttachmentFileFormat.getValue(), reportParams, null, emailCampaign.getApprovedBy(), errorLog); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignWritePlatformService.java index 9819131f9..46601de90 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignWritePlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignWritePlatformService.java @@ -18,11 +18,14 @@ */ package org.apache.fineract.infrastructure.campaigns.email.service; +import java.util.HashMap; import org.apache.fineract.infrastructure.campaigns.email.data.PreviewCampaignMessage; +import org.apache.fineract.infrastructure.campaigns.email.domain.EmailCampaign; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.api.JsonQuery; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; public interface EmailCampaignWritePlatformService { @@ -44,4 +47,6 @@ public interface EmailCampaignWritePlatformService { void sendEmailMessage() throws JobExecutionException; + void insertDirectCampaignIntoEmailOutboundTable(Loan loan, EmailCampaign emailCampaign, HashMap campaignParams); + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailMessageJobEmailServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailMessageJobEmailServiceImpl.java index 519464405..c9abac45d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailMessageJobEmailServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailMessageJobEmailServiceImpl.java @@ -23,10 +23,9 @@ import java.util.List; import java.util.Properties; import javax.mail.MessagingException; import javax.mail.internet.MimeMessage; -import org.apache.fineract.infrastructure.campaigns.email.EmailApiConstants; import org.apache.fineract.infrastructure.campaigns.email.data.EmailMessageWithAttachmentData; -import org.apache.fineract.infrastructure.campaigns.email.domain.EmailConfiguration; -import org.apache.fineract.infrastructure.campaigns.email.domain.EmailConfigurationRepository; +import org.apache.fineract.infrastructure.configuration.data.SMTPCredentialsData; +import org.apache.fineract.infrastructure.configuration.service.ExternalServicesPropertiesReadPlatformService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -38,30 +37,33 @@ import org.springframework.stereotype.Service; public final class EmailMessageJobEmailServiceImpl implements EmailMessageJobEmailService { private static final Logger LOG = LoggerFactory.getLogger(EmailMessageJobEmailServiceImpl.class); - private EmailConfigurationRepository emailConfigurationRepository; + private final ExternalServicesPropertiesReadPlatformService externalServicesReadPlatformService; @Autowired - private EmailMessageJobEmailServiceImpl(final EmailConfigurationRepository emailConfigurationRepository) { - this.emailConfigurationRepository = emailConfigurationRepository; + private EmailMessageJobEmailServiceImpl(ExternalServicesPropertiesReadPlatformService externalServicesReadPlatformService) { + this.externalServicesReadPlatformService = externalServicesReadPlatformService; } @Override public void sendEmailWithAttachment(EmailMessageWithAttachmentData emailMessageWithAttachmentData) { + final SMTPCredentialsData smtpCredentialsData = this.externalServicesReadPlatformService.getSMTPCredentials(); try { JavaMailSenderImpl javaMailSenderImpl = new JavaMailSenderImpl(); - javaMailSenderImpl.setHost(this.getGmailSmtpServer()); - javaMailSenderImpl.setPort(this.getGmailSmtpPort()); - javaMailSenderImpl.setUsername(this.getGmailSmtpUsername()); - javaMailSenderImpl.setPassword(this.getGmailSmtpPassword()); - javaMailSenderImpl.setJavaMailProperties(this.getJavaMailProperties()); + javaMailSenderImpl.setHost(smtpCredentialsData.getHost()); + javaMailSenderImpl.setPort(Integer.parseInt(smtpCredentialsData.getPort())); + javaMailSenderImpl.setUsername(smtpCredentialsData.getUsername()); + javaMailSenderImpl.setPassword(smtpCredentialsData.getPassword()); + javaMailSenderImpl + .setJavaMailProperties(this.getJavaMailProperties(smtpCredentialsData, javaMailSenderImpl.getJavaMailProperties())); MimeMessage mimeMessage = javaMailSenderImpl.createMimeMessage(); // use the true flag to indicate you need a multipart message MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true); + mimeMessageHelper.setFrom(smtpCredentialsData.getFromEmail()); mimeMessageHelper.setTo(emailMessageWithAttachmentData.getTo()); - mimeMessageHelper.setText(emailMessageWithAttachmentData.getText()); + mimeMessageHelper.setText(emailMessageWithAttachmentData.getText(), true); mimeMessageHelper.setSubject(emailMessageWithAttachmentData.getSubject()); final List attachments = emailMessageWithAttachmentData.getAttachments(); if (attachments != null && attachments.size() > 0) { @@ -80,32 +82,18 @@ public final class EmailMessageJobEmailServiceImpl implements EmailMessageJobEma } - private String getGmailSmtpServer() { - final EmailConfiguration gmailSmtpServer = this.emailConfigurationRepository.findByName(EmailApiConstants.SMTP_SERVER); - return (gmailSmtpServer != null) ? gmailSmtpServer.getValue() : null; - } - - private Integer getGmailSmtpPort() { - final EmailConfiguration gmailSmtpPort = this.emailConfigurationRepository.findByName(EmailApiConstants.SMTP_PORT); - return (gmailSmtpPort != null) ? Integer.parseInt(gmailSmtpPort.getValue()) : null; - } - - private String getGmailSmtpUsername() { - final EmailConfiguration gmailSmtpUsername = this.emailConfigurationRepository.findByName(EmailApiConstants.SMTP_USERNAME); - return (gmailSmtpUsername != null) ? gmailSmtpUsername.getValue() : null; - } - - private String getGmailSmtpPassword() { - final EmailConfiguration gmailSmtpPassword = this.emailConfigurationRepository.findByName(EmailApiConstants.SMTP_PASSWORD); - return (gmailSmtpPassword != null) ? gmailSmtpPassword.getValue() : null; - } - - private Properties getJavaMailProperties() { - Properties properties = new Properties(); - properties.setProperty("mail.smtp.starttls.enable", "true"); - properties.setProperty("mail.smtp.auth", "true"); - properties.setProperty("mail.smtp.ssl.trust", this.getGmailSmtpServer()); - + private Properties getJavaMailProperties(SMTPCredentialsData smtpCredentialsData, Properties properties) { + properties.put("mail.smtp.starttls.enable", "true"); + properties.put("mail.transport.protocol", "smtp"); + properties.put("mail.smtp.auth", "true"); + properties.put("mail.smtp.ssl.trust", smtpCredentialsData.getHost()); + if (smtpCredentialsData.isUseTLS()) { + // Needs to disable startTLS if the port is 465 in order to send the email successfully when using the + // smtp.gmail.com as the host + if (smtpCredentialsData.getPort().equals("465")) { + properties.put("mail.smtp.starttls.enable", "false"); + } + } return properties; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/address/serialization/AddressCommandFromApiJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/address/serialization/AddressCommandFromApiJsonDeserializer.java index 9b8d37eeb..39eef97e5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/address/serialization/AddressCommandFromApiJsonDeserializer.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/address/serialization/AddressCommandFromApiJsonDeserializer.java @@ -74,9 +74,7 @@ public class AddressCommandFromApiJsonDeserializer { final List configData = configurationData.stream().filter(FieldConfigurationData::isEnabled) .collect(Collectors.toList()); - final Set supportedParameters = configData.stream() - .map(FieldConfigurationData::getField) - .collect(Collectors.toSet()); + final Set supportedParameters = configData.stream().map(FieldConfigurationData::getField).collect(Collectors.toSet()); supportedParameters.add("locale"); supportedParameters.add("dateFormat"); @@ -85,8 +83,7 @@ public class AddressCommandFromApiJsonDeserializer { this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParameters); configData.forEach(fieldConfiguration -> { - final String field = - fieldConfiguration.getField().equals("addressType") ? "addressTypeId" : fieldConfiguration.getField(); + final String field = fieldConfiguration.getField().equals("addressType") ? "addressTypeId" : fieldConfiguration.getField(); final String fieldValue = this.fromApiJsonHelper.extractStringNamed(field, element); if (fieldConfiguration.getField().equals("addressType") && fromNewClient) { diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V367__reports_for_email_campaigns.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V367__reports_for_email_campaigns.sql new file mode 100644 index 000000000..86a97fc87 --- /dev/null +++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V367__reports_for_email_campaigns.sql @@ -0,0 +1,26 @@ +-- +-- Licensed to the Apache Software Foundation (ASF) under one +-- or more contributor license agreements. See the NOTICE file +-- distributed with this work for additional information +-- regarding copyright ownership. The ASF licenses this file +-- to you 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. +-- + +INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_sql`, `description`, `core_report`, `use_report`, `self_service_user_report`) VALUES ('Loan Approved - Email', 'Email', 'Triggered', 'select ml.id as loanId, ifnull(mc.id,mc2.id) as id, ifnull(mc.firstname,mc2.firstname) as firstname, \nifnull(mc.middlename,ifnull(mc2.middlename,(\'\'))) as middlename, ifnull(mc.lastname,mc2.lastname) as lastname, \nifnull(mc.display_name,mc2.display_name) as display_name, ifnull(mc.status_enum,mc2.status_enum) as status_enum, \nifnull(mc.mobile_no,mc2.mobile_no) as mobile_no, ifnull(mg.office_id,mc2.office_id) as office_id, ifnull(mg.staff_id,mc2.staff_id) as staff_id, \nmg.id as group_id, mg.display_name as group_name, ifnull(mc.email_address,mc2.email_address) as emailAddress\nfrom m_loan ml left join m_group mg on mg.id = ml.group_id \nleft join m_group_client mgc on mgc.group_id = mg.id \nleft join m_client mc on mc.id = mgc.client_id \nleft join m_client mc2 on mc2.id = ml.client_id\nWHERE (mc.status_enum = 300 or mc2.status_enum = 300) and (mc.email_address is not null or mc2.email_address is not null) and ml.id = ${loanId}\n', 'Loan and client data of approved loan', '0', '1', '0'); +INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_sql`, `description`, `core_report`, `use_report`, `self_service_user_report`) VALUES ('Loan Rejected - Email', 'Email', 'Triggered', 'select ml.id as loanId, ifnull(mc.id,mc2.id) as id, ifnull(mc.firstname,mc2.firstname) as firstname, \nifnull(mc.middlename,ifnull(mc2.middlename,(\'\'))) as middlename, ifnull(mc.lastname,mc2.lastname) as lastname, \nifnull(mc.display_name,mc2.display_name) as display_name, ifnull(mc.status_enum,mc2.status_enum) as status_enum, \nifnull(mc.mobile_no,mc2.mobile_no) as mobile_no, ifnull(mg.office_id,mc2.office_id) as office_id, ifnull(mg.staff_id,mc2.staff_id) as staff_id, \nmg.id as group_id, mg.display_name as group_name, ifnull(mc.email_address,mc2.email_address) as emailAddress\nfrom m_loan ml left join m_group mg on mg.id = ml.group_id \nleft join m_group_client mgc on mgc.group_id = mg.id \nleft join m_client mc on mc.id = mgc.client_id \nleft join m_client mc2 on mc2.id = ml.client_id\nWHERE (mc.status_enum = 300 or mc2.status_enum = 300) and (mc.email_address is not null or mc2.email_address is not null) and ml.id = ${loanId}\n', 'Loan and client data of rejected loan', '0', '1', '0'); +INSERT INTO `stretchy_report` (`report_name`, `report_type`, `report_subtype`, `report_sql`, `description`, `core_report`, `use_report`, `self_service_user_report`) VALUES ('Loan Repayment - Email', 'Email', 'Triggered', 'select ml.id as loanId, ifnull(mc.id,mc2.id) as id, ifnull(mc.firstname,mc2.firstname) as firstname, \nifnull(mc.middlename,ifnull(mc2.middlename,(\'\'))) as middlename, ifnull(mc.lastname,mc2.lastname) as lastname, \nifnull(mc.display_name,mc2.display_name) as display_name, ifnull(mc.status_enum,mc2.status_enum) as status_enum, \nifnull(mc.mobile_no,mc2.mobile_no) as mobile_no, ifnull(mg.office_id,mc2.office_id) as office_id, ifnull(mg.staff_id,mc2.staff_id) as staff_id, \nmg.id as group_id, mg.display_name as group_name, ifnull(mc.email_address,mc2.email_address) as emailAddress, lt.amount as repaymentAmount \nfrom m_loan_transaction lt join m_loan ml on ml.id=lt.loan_id left join m_group mg on mg.id = ml.group_id \nleft join m_group_client mgc on mgc.group_id = mg.id \nleft join m_client mc on mc.id = mgc.client_id \nleft join m_client mc2 on mc2.id = ml.client_id\nWHERE (mc.status_enum = 300 or mc2.status_enum = 300) and (mc.email_address is not null or mc2.email_address is not null) and ml.id = ${loanId} and lt.id = ${loanTransactionId}\n', 'Loan and client data of rejected loan', '0', '1', '0'); + +ALTER TABLE `scheduled_email_campaign` +CHANGE COLUMN `email_attachment_file_format` `email_attachment_file_format` VARCHAR(10) NULL , +CHANGE COLUMN `stretchy_report_id` `stretchy_report_id` INT(11) NULL ; diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V367__message_gateway_hook_template.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V370__message_gateway_hook_template.sql similarity index 100% rename from fineract-provider/src/main/resources/sql/migrations/core_db/V367__message_gateway_hook_template.sql rename to fineract-provider/src/main/resources/sql/migrations/core_db/V370__message_gateway_hook_template.sql diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/ReportsTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/ReportsTest.java index f1eda5fe9..98378b14a 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/ReportsTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/ReportsTest.java @@ -35,7 +35,7 @@ public class ReportsTest extends IntegrationTest { @Test void listReports() { - assertThat(ok(fineract().reports.retrieveReportList())).hasSize(117); + assertThat(ok(fineract().reports.retrieveReportList())).hasSize(120); } @Test