diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm
index f1a319775..4ab7a5cee 100644
--- a/api-docs/apiLive.htm
+++ b/api-docs/apiLive.htm
@@ -3056,6 +3056,30 @@
+
+ Report Mailing Job
+ reportmailingjob
+ Create a Report Mailing Job
+ List Reports Mailing Jobs
+
+
+
+
+
+ reportmailingjob/{id}
+
+ Retrieve a Report Mailing Job
+ Update a Report Mailing Job
+ Delete a Report Mailing Job
+
+
+
+ reportmailingjobrunhistory/{jobId}
+
+ Retrieve Reports Mailing Job History
+
+
+
@@ -34297,6 +34321,426 @@ GET https://DomainName/api/v1/runreports/{reportName}
+
+
+
+
+
+
Report Mailing Jobs
+
This resource allows you to create a scheduled job that runs a report and sents it by email to specified email addresses.
+
The scheduled job can be configured to run once or on a regular basis (once a day, twice a week, etc).
+
+
+ Field Descriptions
+
+
+ name
+
+
+ The name of the report mailing job. It must be unique.
+
+
+ description
+
+
+ Optional: Description of the report mailing job.
+
+
+ startDateTime
+
+
+ Date and time to start the report mailing job.
+
+
+ stretchyReportId
+
+
+ The identifier of the stretchy report to be sent.
+
+
+ emailRecipients
+
+
+ Comma separated report recipient email addresses.
+
+
+ emailSubject
+
+
+ The subject of the email to be sent.
+
+
+ emailMessage
+
+
+ The body of the email to be sent.
+
+
+ emailAttachmentFileFormatId
+
+
+ The Enum constant id of the email attachment file format.
+
+
+ recurrence
+
+
+ Rule or repeating pattern for recurring events. See - http://www.kanzaki.com/docs/ical/rrule.html
+
+
+ isActive
+
+
+ Indicates whether or not the scheduler should be created as active.
+
+
+ stretchyReportParamMap
+
+
+ Optional: A map of the stretchy report parameter names to values.
+
+
+
+
+
+
+
+
+
Retrieve Report Mailing Job Details Template
+
This is a convenience resource. It can be useful when
+ building maintenance user interface screens for report mailing job
+ applications. The template data returned consists of any or all
+ of:
+
+ Field Defaults
+ Allowed Value Lists
+
+
+
Example Request:
+
reportmailingjobs/template
+
+
+
+GET https://DomainName/api/v1/reportmailingjobs/template
+
+
+{
+ "isActive": false,
+ "emailAttachmentFileFormatOptions": [
+ {
+ "id": 1,
+ "code": "ReportMailingJobEmailAttachmentFileFormat.xls",
+ "value": "xls"
+ },
+ {
+ "id": 2,
+ "code": "ReportMailingJobEmailAttachmentFileFormat.pdf",
+ "value": "pdf"
+ },
+ {
+ "id": 3,
+ "code": "ReportMailingJobEmailAttachmentFileFormat.csv",
+ "value": "csv"
+ }
+ ],
+ "stretchyReportParamDateOptions": [
+ {
+ "id": 1,
+ "code": "reportMailingJobStretchyReportParamDateOption.today",
+ "value": "today"
+ },
+ {
+ "id": 2,
+ "code": "reportMailingJobStretchyReportParamDateOption.yesterday",
+ "value": "yesterday"
+ },
+ {
+ "id": 3,
+ "code": "reportMailingJobStretchyReportParamDateOption.tomorrow",
+ "value": "tomorrow"
+ }
+ ]
+}
+
+
+
+
+
+
+
+
Retrieve a Report Mailing Job
+
Example Requests:
+
reportmailingjobs/1
+
+
+
reportmailingjobs/1?template=true
+
+
+
+GET https://DomainName/api/v1/reportmailingjobs/{id}
+
+
+{
+ "id": 1,
+ "name": "Client Numbers Report",
+ "description": "Client Numbers Report",
+ "startDateTime": 1469627093000,
+ "recurrence": "",
+ "timeline": {
+ "createdOnDate": [
+ 2016,
+ 7,
+ 27
+ ],
+ "createdByUsername": "musoni",
+ "createdByFirstname": "firstname",
+ "createdByLastname": "lastname"
+ },
+ "emailRecipients": "info@musonisystem.com",
+ "emailSubject": "Client Numbers Report",
+ "emailMessage": "Client Numbers Report",
+ "emailAttachmentFileFormat": {
+ "id": 1,
+ "code": "ReportMailingJobEmailAttachmentFileFormat.xls",
+ "value": "xls"
+ },
+ "stretchyReport": {
+ "id": 120,
+ "reportName": "Client Numbers Report",
+ "reportType": "Pentaho",
+ "reportCategory": "Client",
+ "description": "",
+ "coreReport": false,
+ "useReport": true
+ },
+ "stretchyReportParamMap": "{\"startDate\":\"2016-07-01\",\"endDate\":\"2016-08-02\",\"selectOffice\":\"1\",\"environementUrl\":\"environementUrl\"}",
+ "nextRunDateTime": 1469627093000,
+ "numberOfRuns": 0,
+ "isActive": true,
+ "runAsUserId": 1
+}
+
+
+
+
+
+
+
+
List Report Mailing Jobs
+
Example Requests:
+
reportmailingjobs
+
+
+
+GET https://DomainName/api/v1/reportmailingjobs
+
+
+[
+ {
+ "id": 1,
+ "name": "Client Numbers Report",
+ "description": "Client Numbers Report",
+ "startDateTime": 1469627093000,
+ "recurrence": "",
+ "timeline": {
+ "createdOnDate": [
+ 2016,
+ 7,
+ 27
+ ],
+ "createdByUsername": "musoni",
+ "createdByFirstname": "firstname",
+ "createdByLastname": "lastname"
+ },
+ "emailRecipients": "info@musonisystem.com",
+ "emailSubject": "Client Numbers Report",
+ "emailMessage": "Client Numbers Report",
+ "emailAttachmentFileFormat": {
+ "id": 1,
+ "code": "ReportMailingJobEmailAttachmentFileFormat.xls",
+ "value": "xls"
+ },
+ "stretchyReport": {
+ "id": 120,
+ "reportName": "Client Numbers Report",
+ "reportType": "Pentaho",
+ "reportCategory": "Client",
+ "description": "",
+ "coreReport": false,
+ "useReport": true
+ },
+ "stretchyReportParamMap": "{\"startDate\":\"2016-07-01\",\"endDate\":\"2016-08-02\",\"selectOffice\":\"1\",\"environementUrl\":\"environementUrl\"}",
+ "nextRunDateTime": 1469627093000,
+ "numberOfRuns": 0,
+ "isActive": true,
+ "runAsUserId": 1
+ }
+]
+
+
+
+
+
+
+
+
List Report Mailing Job History
+
The list capability of report mailing job history can support pagination and sorting .
+
Optional Arguments
+
+ offset
+
+ Integer optional , defaults to 0
+
+ Indicates the result from which pagination starts
+
+ limit
+
+ Integer optional , defaults to 200
+
+ Restricts the size of results returned. To override the default and return all entries you must explicitly pass a non-positive integer value for limit e.g. limit=0, or limit=-1
+
+ orderBy
+
+ String optional , one of startDateTime, endDateTime, status
+
+ Orders results by the indicated field.
+
+ sortBy
+
+ String optional , one of ASC, DESC
+
+ Indicates what way to order results if orderBy is used.
+
+
Example Requests:
+
reportmailingjobrunhistory/1
+
+
+
+GET https://DomainName/api/v1/reportmailingjobrunhistory/{reportMailingJobId}
+
+
+[
+ {
+ "id": 1,
+ "reportMailingJobId": 1,
+ "startDateTime": 1469627093000",
+ "endDateTime": 1469627093050,
+ "status": "success",
+ "errorMessage": "",
+ "errorLog": ""
+ }
+]
+
+
+
+
+
+
+
+
Create a Report Mailing Job
+
+
+ Mandatory Fields
+
+
+ name, startDateTime, stretchyReportId, emailRecipients, emailSubject, emailMessage, emailAttachmentFileFormatId, recurrence, isActive
+
+
+
+
+
+ Optional Fields
+
+
+ description, stretchyReportParamMap
+
+
+
+
+
+POST https://DomainName/api/v1/reportmailingjobs
+
+
+POST reportmailingjobs
+Content-Type: application/json Request Body:
+{
+ "locale": "en_GB",
+ "dateFormat": "dd-MM-yyyy HH:mm:ss",
+ "name": "Client Numbers Report",
+ "description": "Client Numbers Report",
+ "startDateTime": "02-08-2016 11:34:18",
+ "stretchyReportId": "120",
+ "emailRecipients": "info@musonisystem.com",
+ "emailSubject": "Client Numbers Report",
+ "emailMessage": "Client Numbers Report",
+ "emailAttachmentFileFormatId": "1",
+ "recurrence": "FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,WE,FR",
+ "isActive": true,
+ "stretchyReportParamMap": "{\"startDate\":\"2016-07-01\",\"endDate\":\"2016-08-02\",\"selectOffice\":\"1\",\"environementUrl\":\"environementUrl\"}"
+}
+
+
+{
+ "resourceId": 1
+}
+
+
+
+
+
+
+
+
Update a Report Mailing Job
+
+
+
+PUT https://DomainName/api/v1/reportmailingjobs/{id}
+
+
+PUT reportmailingjobs/1
+Content-Type: application/json
+Request Body:
+{
+ "locale": "en_GB",
+ "dateFormat": "dd-MM-yyyy HH:mm:ss",
+ "startDateTime": "10-08-2016 23:30:00"
+}
+
+
+
+{
+ "resourceId": 1,
+ "changes": {
+ "startDateTime": "10-08-2016 23:30:00"
+ }
+}
+
+
+
+
+
+
+
+
Delete a Report Mailing Job
+
+
+
+DELETE https://DomainName/api/v1/reportmailingjobs/{id}
+
+
+DELETE reportmailingjobs/1
+Content-Type: application/json
+Request Body:
+{
+}
+
+
+{
+ "resourceId": 1
+}
+
+
+
diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
index dea6a9e42..2d678b15d 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
@@ -2755,4 +2755,27 @@ public class CommandWrapperBuilder {
return this;
}
-}
\ No newline at end of file
+ public CommandWrapperBuilder createReportMailingJob(final String entityName) {
+ this.actionName = "CREATE";
+ this.entityName = entityName;
+ this.entityId = null;
+ this.href = "/reportmailingjobs";
+ return this;
+ }
+
+ public CommandWrapperBuilder updateReportMailingJob(final String entityName, final Long entityId) {
+ this.actionName = "UPDATE";
+ this.entityName = entityName;
+ this.entityId = entityId;
+ this.href = "/reportmailingjobs/" + entityId;
+ return this;
+ }
+
+ public CommandWrapperBuilder deleteReportMailingJob(final String entityName, final Long entityId) {
+ this.actionName = "DELETE";
+ this.entityName = entityName;
+ this.entityId = entityId;
+ this.href = "/reportmailingjobs/" + entityId;
+ return this;
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/SearchParameters.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/SearchParameters.java
index 97c98ba9c..c1da25245 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/SearchParameters.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/SearchParameters.java
@@ -443,4 +443,33 @@ public final class SearchParameters {
return this.isSelfUser;
}
-}
\ No newline at end of file
+ /**
+ * creates an instance of the SearchParameters from a request for the report mailing job run history
+ *
+ * @return SearchParameters object
+ **/
+ public static SearchParameters fromReportMailingJobRunHistory(final Integer offset,
+ final Integer limit, final String orderBy, final String sortOrder) {
+ final Integer maxLimitAllowed = getCheckedLimit(limit);
+
+ return new SearchParameters(null, null, null, null, null, null, null, offset, maxLimitAllowed, orderBy,
+ sortOrder, null, null, null, null, null, false);
+ }
+
+ /**
+ * creates an instance of the {@link SearchParameters} from a request for the report mailing job
+ *
+ * @param offset
+ * @param limit
+ * @param orderBy
+ * @param sortOrder
+ * @return {@link SearchParameters} object
+ */
+ public static SearchParameters fromReportMailingJob(final Integer offset,
+ final Integer limit, final String orderBy, final String sortOrder) {
+ final Integer maxLimitAllowed = getCheckedLimit(limit);
+
+ return new SearchParameters(null, null, null, null, null, null, null, offset, maxLimitAllowed, orderBy,
+ sortOrder, null, null, null, null, null, false);
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/domain/ReportRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/domain/ReportRepositoryWrapper.java
new file mode 100644
index 000000000..6421053b9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/domain/ReportRepositoryWrapper.java
@@ -0,0 +1,54 @@
+/**
+ * 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.dataqueries.domain;
+
+import org.apache.fineract.infrastructure.dataqueries.exception.ReportNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * A wrapper class for the ReportRepository that provides a method that returns a Report entity if it exists,
+ * else throws "ReportNotFoundException" exception if the Report does not exist
+ **/
+@Service
+public class ReportRepositoryWrapper {
+ private final ReportRepository reportRepository;
+
+ @Autowired
+ public ReportRepositoryWrapper(final ReportRepository reportRepository) {
+ this.reportRepository = reportRepository;
+ }
+
+ /**
+ * Retrieves an entity by its id
+ *
+ * @param id must not be null
+ * @throws ReportNotFoundException if entity not found
+ * @return {@link Report} object
+ */
+ public Report findOneThrowExceptionIfNotFound(final Long id) {
+ final Report report = this.reportRepository.findOne(id);
+
+ if (report == null) {
+ throw new ReportNotFoundException(id);
+ }
+
+ return report;
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingServiceImpl.java
index 69c805638..a6e97aed8 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingServiceImpl.java
@@ -484,5 +484,4 @@ public class ReadReportingServiceImpl implements ReadReportingService {
return new ReportParameterData(id, null, null, parameterName);
}
}
-
-}
\ No newline at end of file
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java
index 0580a4c4e..d893c3b68 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java
@@ -41,7 +41,8 @@ public enum JobName {
GENERATE_LOANLOSS_PROVISIONING("Generate Loan Loss Provisioning"), //
POST_DIVIDENTS_FOR_SHARES("Post Dividends For Shares"), //
UPDATE_SAVINGS_DORMANT_ACCOUNTS("Update Savings Dormant Accounts"), //
- ADD_PERIODIC_ACCRUAL_ENTRIES_FOR_LOANS_WITH_INCOME_POSTED_AS_TRANSACTIONS("Add Accrual Transactions For Loans With Income Posted As Transactions");
+ ADD_PERIODIC_ACCRUAL_ENTRIES_FOR_LOANS_WITH_INCOME_POSTED_AS_TRANSACTIONS("Add Accrual Transactions For Loans With Income Posted As Transactions"),
+ EXECUTE_REPORT_MAILING_JOBS("Execute Report Mailing Jobs");
private final String name;
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/ReportMailingJobConstants.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/ReportMailingJobConstants.java
new file mode 100644
index 000000000..ab5f11082
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/ReportMailingJobConstants.java
@@ -0,0 +1,88 @@
+/**
+ * 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.reportmailingjob;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class ReportMailingJobConstants {
+
+ // define the API resource entity name
+ public static final String REPORT_MAILING_JOB_ENTITY_NAME = "REPORTMAILINGJOB";
+
+ // define the API resource name for the report mailing job resource
+ public static final String REPORT_MAILING_JOB_RESOURCE_NAME = "reportmailingjobs";
+
+ // define the API resource name for the report mailing job history resource
+ public static final String REPORT_MAILING_JOB_RUN_HISTORY_RESOURCE_NAME = "reportmailingjobrunhistory";
+
+ // general API resource request parameter constants
+ public static final String LOCALE_PARAM_NAME = "locale";
+ public static final String DATE_FORMAT_PARAM_NAME = "dateFormat";
+
+ // parameter constants for create/update entity API request
+ public static final String NAME_PARAM_NAME = "name";
+ public static final String DESCRIPTION_PARAM_NAME = "description";
+ public static final String START_DATE_TIME_PARAM_NAME = "startDateTime";
+ public static final String RECURRENCE_PARAM_NAME = "recurrence";
+ public static final String EMAIL_RECIPIENTS_PARAM_NAME = "emailRecipients";
+ public static final String EMAIL_SUBJECT_PARAM_NAME = "emailSubject";
+ public static final String EMAIL_MESSAGE_PARAM_NAME = "emailMessage";
+ public static final String EMAIL_ATTACHMENT_FILE_FORMAT_ID_PARAM_NAME = "emailAttachmentFileFormatId";
+ public static final String STRETCHY_REPORT_ID_PARAM_NAME = "stretchyReportId";
+ public static final String STRETCHY_REPORT_PARAM_MAP_PARAM_NAME = "stretchyReportParamMap";
+ public static final String IS_ACTIVE_PARAM_NAME = "isActive";
+
+ // other parameter constants
+ public static final String ID_PARAM_NAME = "id";
+ public static final String EMAIL_ATTACHMENT_FILE_FORMAT_PARAM_NAME = "emailAttachmentFileFormat";
+ public static final String PREVIOUS_RUN_DATE_TIME_PARAM_NAME = "previousRunDateTime";
+ public static final String NEXT_RUN_DATE_TIME_PARAM_NAME = "nextRunDateTime";
+ public static final String PREVIOUS_RUN_STATUS = "previousRunStatus";
+ public static final String PREVIOUS_RUN_ERROR_LOG = "previousRunErrorLog";
+ public static final String PREVIOUS_RUN_ERROR_MESSAGE = "previousRunErrorMessage";
+ public static final String NUMBER_OF_RUNS = "numberOfRuns";
+ public static final String STRETCHY_REPORT_PARAM_NAME = "stretchyReport";
+
+ // list of permitted parameters for the create report mailing job request
+ public static final Set CREATE_REQUEST_PARAMETERS = new HashSet<>(Arrays.asList(LOCALE_PARAM_NAME, DATE_FORMAT_PARAM_NAME,
+ NAME_PARAM_NAME, DESCRIPTION_PARAM_NAME, RECURRENCE_PARAM_NAME, EMAIL_RECIPIENTS_PARAM_NAME, EMAIL_SUBJECT_PARAM_NAME,
+ EMAIL_MESSAGE_PARAM_NAME, EMAIL_ATTACHMENT_FILE_FORMAT_ID_PARAM_NAME, STRETCHY_REPORT_ID_PARAM_NAME,
+ STRETCHY_REPORT_PARAM_MAP_PARAM_NAME, IS_ACTIVE_PARAM_NAME, START_DATE_TIME_PARAM_NAME));
+
+ // list of permitted parameters for the update report mailing job request
+ public static final Set UPDATE_REQUEST_PARAMETERS = new HashSet<>(Arrays.asList(LOCALE_PARAM_NAME, DATE_FORMAT_PARAM_NAME,
+ NAME_PARAM_NAME, DESCRIPTION_PARAM_NAME, RECURRENCE_PARAM_NAME, EMAIL_RECIPIENTS_PARAM_NAME, EMAIL_SUBJECT_PARAM_NAME,
+ EMAIL_MESSAGE_PARAM_NAME, EMAIL_ATTACHMENT_FILE_FORMAT_ID_PARAM_NAME, STRETCHY_REPORT_ID_PARAM_NAME,
+ STRETCHY_REPORT_PARAM_MAP_PARAM_NAME, IS_ACTIVE_PARAM_NAME, START_DATE_TIME_PARAM_NAME));
+
+ // list of parameters that represent the properties of a report mailing job
+ public static final Set REPORT_MAILING_JOB_DATA_PARAMETERS = new HashSet<>(Arrays.asList(ID_PARAM_NAME, NAME_PARAM_NAME,
+ DESCRIPTION_PARAM_NAME, RECURRENCE_PARAM_NAME, EMAIL_RECIPIENTS_PARAM_NAME, EMAIL_SUBJECT_PARAM_NAME, EMAIL_MESSAGE_PARAM_NAME,
+ EMAIL_ATTACHMENT_FILE_FORMAT_PARAM_NAME, STRETCHY_REPORT_PARAM_NAME, STRETCHY_REPORT_PARAM_MAP_PARAM_NAME, IS_ACTIVE_PARAM_NAME,
+ START_DATE_TIME_PARAM_NAME, PREVIOUS_RUN_DATE_TIME_PARAM_NAME, NEXT_RUN_DATE_TIME_PARAM_NAME, PREVIOUS_RUN_STATUS,
+ PREVIOUS_RUN_ERROR_LOG, PREVIOUS_RUN_ERROR_MESSAGE, NUMBER_OF_RUNS));
+
+ // report mailing job configuration names
+ public static final String GMAIL_SMTP_SERVER = "GMAIL_SMTP_SERVER";
+ public static final String GMAIL_SMTP_PORT = "GMAIL_SMTP_PORT";
+ public static final String GMAIL_SMTP_USERNAME = "GMAIL_SMTP_USERNAME";
+ public static final String GMAIL_SMTP_PASSWORD = "GMAIL_SMTP_PASSWORD";
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/api/ReportMailingJobApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/api/ReportMailingJobApiResource.java
new file mode 100644
index 000000000..da6da6d8f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/api/ReportMailingJobApiResource.java
@@ -0,0 +1,162 @@
+/**
+ * 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.reportmailingjob.api;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.infrastructure.reportmailingjob.ReportMailingJobConstants;
+import org.apache.fineract.infrastructure.reportmailingjob.data.ReportMailingJobData;
+import org.apache.fineract.infrastructure.reportmailingjob.service.ReportMailingJobReadPlatformService;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/" + ReportMailingJobConstants.REPORT_MAILING_JOB_RESOURCE_NAME)
+@Component
+@Scope("singleton")
+public class ReportMailingJobApiResource {
+
+ private final PlatformSecurityContext platformSecurityContext;
+ private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+ private final ApiRequestParameterHelper apiRequestParameterHelper;
+ private final DefaultToApiJsonSerializer reportMailingToApiJsonSerializer;
+ private final ReportMailingJobReadPlatformService reportMailingJobReadPlatformService;
+
+ @Autowired
+ public ReportMailingJobApiResource(final PlatformSecurityContext platformSecurityContext,
+ final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+ final ApiRequestParameterHelper apiRequestParameterHelper,
+ final DefaultToApiJsonSerializer reportMailingToApiJsonSerializer,
+ final ReportMailingJobReadPlatformService reportMailingJobReadPlatformService) {
+ this.platformSecurityContext = platformSecurityContext;
+ this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+ this.apiRequestParameterHelper = apiRequestParameterHelper;
+ this.reportMailingToApiJsonSerializer = reportMailingToApiJsonSerializer;
+ this.reportMailingJobReadPlatformService = reportMailingJobReadPlatformService;
+ }
+
+ @POST
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ public String createReportMailingJob(final String apiRequestBodyAsJson) {
+ final CommandWrapper commandWrapper = new CommandWrapperBuilder().
+ createReportMailingJob(ReportMailingJobConstants.REPORT_MAILING_JOB_ENTITY_NAME).
+ withJson(apiRequestBodyAsJson).build();
+
+ final CommandProcessingResult commandProcessingResult = this.commandsSourceWritePlatformService.logCommandSource(commandWrapper);
+
+ return this.reportMailingToApiJsonSerializer.serialize(commandProcessingResult);
+ }
+
+ @PUT
+ @Path("{entityId}")
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ public String updateReportMailingJob(@PathParam("entityId") final Long entityId, final String apiRequestBodyAsJson) {
+ final CommandWrapper commandWrapper = new CommandWrapperBuilder().
+ updateReportMailingJob(ReportMailingJobConstants.REPORT_MAILING_JOB_ENTITY_NAME, entityId).
+ withJson(apiRequestBodyAsJson).build();
+
+ final CommandProcessingResult commandProcessingResult = this.commandsSourceWritePlatformService.logCommandSource(commandWrapper);
+
+ return this.reportMailingToApiJsonSerializer.serialize(commandProcessingResult);
+ }
+
+ @DELETE
+ @Path("{entityId}")
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ public String deleteReportMailingJob(@PathParam("entityId") final Long entityId, final String apiRequestBodyAsJson) {
+ final CommandWrapper commandWrapper = new CommandWrapperBuilder().
+ deleteReportMailingJob(ReportMailingJobConstants.REPORT_MAILING_JOB_ENTITY_NAME, entityId).
+ withJson(apiRequestBodyAsJson).build();
+
+ final CommandProcessingResult commandProcessingResult = this.commandsSourceWritePlatformService.logCommandSource(commandWrapper);
+
+ return this.reportMailingToApiJsonSerializer.serialize(commandProcessingResult);
+ }
+
+ @GET
+ @Path("{entityId}")
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ public String retrieveReportMailingJob(@PathParam("entityId") final Long entityId, @Context final UriInfo uriInfo) {
+ this.platformSecurityContext.authenticatedUser().validateHasReadPermission(ReportMailingJobConstants.REPORT_MAILING_JOB_ENTITY_NAME);
+
+ final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+ ReportMailingJobData reportMailingJobData = this.reportMailingJobReadPlatformService.retrieveReportMailingJob(entityId);
+
+ if (settings.isTemplate()) {
+ final ReportMailingJobData ReportMailingJobDataOptions = this.reportMailingJobReadPlatformService.retrieveReportMailingJobEnumOptions();
+ reportMailingJobData = ReportMailingJobData.newInstance(reportMailingJobData, ReportMailingJobDataOptions);
+ }
+
+ return this.reportMailingToApiJsonSerializer.serialize(settings, reportMailingJobData, ReportMailingJobConstants.REPORT_MAILING_JOB_DATA_PARAMETERS);
+ }
+
+ @GET
+ @Path("template")
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ public String retrieveReportMailingJobTemplate(@Context final UriInfo uriInfo) {
+ this.platformSecurityContext.authenticatedUser().validateHasReadPermission(ReportMailingJobConstants.REPORT_MAILING_JOB_ENTITY_NAME);
+
+ final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+ final ReportMailingJobData ReportMailingJobDataOptions = this.reportMailingJobReadPlatformService.retrieveReportMailingJobEnumOptions();
+
+ return this.reportMailingToApiJsonSerializer.serialize(settings, ReportMailingJobDataOptions, ReportMailingJobConstants.REPORT_MAILING_JOB_DATA_PARAMETERS);
+ }
+
+ @GET
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ public String retrieveAllReportMailingJobs(@Context final UriInfo uriInfo,
+ @QueryParam("offset") final Integer offset,
+ @QueryParam("limit") final Integer limit, @QueryParam("orderBy") final String orderBy,
+ @QueryParam("sortOrder") final String sortOrder) {
+ this.platformSecurityContext.authenticatedUser().validateHasReadPermission(ReportMailingJobConstants.REPORT_MAILING_JOB_ENTITY_NAME);
+
+ final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+ final SearchParameters searchParameters = SearchParameters.fromReportMailingJob(offset, limit, orderBy, sortOrder);
+ final Page reportMailingJobData = this.reportMailingJobReadPlatformService.retrieveAllReportMailingJobs(searchParameters);
+
+ return this.reportMailingToApiJsonSerializer.serialize(settings, reportMailingJobData, ReportMailingJobConstants.REPORT_MAILING_JOB_DATA_PARAMETERS);
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/api/ReportMailingJobRunHistoryApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/api/ReportMailingJobRunHistoryApiResource.java
new file mode 100644
index 000000000..a7802eee9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/api/ReportMailingJobRunHistoryApiResource.java
@@ -0,0 +1,80 @@
+/**
+ * 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.reportmailingjob.api;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.infrastructure.reportmailingjob.ReportMailingJobConstants;
+import org.apache.fineract.infrastructure.reportmailingjob.data.ReportMailingJobRunHistoryData;
+import org.apache.fineract.infrastructure.reportmailingjob.service.ReportMailingJobRunHistoryReadPlatformService;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/" + ReportMailingJobConstants.REPORT_MAILING_JOB_RUN_HISTORY_RESOURCE_NAME)
+@Component
+@Scope("singleton")
+public class ReportMailingJobRunHistoryApiResource {
+
+ private final PlatformSecurityContext platformSecurityContext;
+ private final ApiRequestParameterHelper apiRequestParameterHelper;
+ private final DefaultToApiJsonSerializer reportMailingToApiJsonSerializer;
+ private final ReportMailingJobRunHistoryReadPlatformService reportMailingJobRunHistoryReadPlatformService;
+
+ @Autowired
+ public ReportMailingJobRunHistoryApiResource(final PlatformSecurityContext platformSecurityContext,
+ final ApiRequestParameterHelper apiRequestParameterHelper,
+ final DefaultToApiJsonSerializer reportMailingToApiJsonSerializer,
+ final ReportMailingJobRunHistoryReadPlatformService reportMailingJobRunHistoryReadPlatformService) {
+ this.platformSecurityContext = platformSecurityContext;
+ this.apiRequestParameterHelper = apiRequestParameterHelper;
+ this.reportMailingToApiJsonSerializer = reportMailingToApiJsonSerializer;
+ this.reportMailingJobRunHistoryReadPlatformService = reportMailingJobRunHistoryReadPlatformService;
+ }
+
+ @GET
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ public String retrieveAllByReportMailingJobId(@Context final UriInfo uriInfo,
+ @QueryParam("reportMailingJobId") final Long reportMailingJobId, @QueryParam("offset") final Integer offset,
+ @QueryParam("limit") final Integer limit, @QueryParam("orderBy") final String orderBy,
+ @QueryParam("sortOrder") final String sortOrder) {
+ this.platformSecurityContext.authenticatedUser().validateHasReadPermission(ReportMailingJobConstants.REPORT_MAILING_JOB_ENTITY_NAME);
+ final SearchParameters searchParameters = SearchParameters.fromReportMailingJobRunHistory(offset, limit, orderBy, sortOrder);
+
+ final Page reportMailingJobRunHistoryData = this.reportMailingJobRunHistoryReadPlatformService.
+ retrieveRunHistoryByJobId(reportMailingJobId, searchParameters);
+ final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+ return this.reportMailingToApiJsonSerializer.serialize(settings, reportMailingJobRunHistoryData);
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/data/ReportMailingJobConfigurationData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/data/ReportMailingJobConfigurationData.java
new file mode 100644
index 000000000..48d075aef
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/data/ReportMailingJobConfigurationData.java
@@ -0,0 +1,67 @@
+/**
+ * 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.reportmailingjob.data;
+
+/**
+ * Immutable data object representing report mailing job configuration data.
+ **/
+public class ReportMailingJobConfigurationData {
+ private final int id;
+ private final String name;
+ private final String value;
+
+ /**
+ * ReportMailingJobConfigurationData private constructor
+ **/
+ private ReportMailingJobConfigurationData(final int id, final String name, final String value) {
+ this.id = id;
+ this.name = name;
+ this.value = value;
+ }
+
+ /**
+ * creates an instance of the ReportMailingJobConfigurationData class
+ *
+ * @return ReportMailingJobConfigurationData object
+ **/
+ public static ReportMailingJobConfigurationData newInstance(final int id, final String name, final String value) {
+ return new ReportMailingJobConfigurationData(id, name, value);
+ }
+
+ /**
+ * @return the id
+ */
+ public int getId() {
+ return id;
+ }
+
+ /**
+ * @return the name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * @return the value
+ */
+ public String getValue() {
+ return value;
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/data/ReportMailingJobData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/data/ReportMailingJobData.java
new file mode 100644
index 000000000..aa5172f2f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/data/ReportMailingJobData.java
@@ -0,0 +1,272 @@
+/**
+ * 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.reportmailingjob.data;
+
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.dataqueries.data.ReportData;
+import org.joda.time.DateTime;
+
+/**
+ * Immutable data object representing report mailing job data.
+ **/
+public class ReportMailingJobData {
+ private final Long id;
+ private final String name;
+ private final String description;
+ private final DateTime startDateTime;
+ private final String recurrence;
+ private final ReportMailingJobTimelineData timeline;
+ private final String emailRecipients;
+ private final String emailSubject;
+ private final String emailMessage;
+ private final EnumOptionData emailAttachmentFileFormat;
+ private final ReportData stretchyReport;
+ private final String stretchyReportParamMap;
+ private final DateTime previousRunDateTime;
+ private final DateTime nextRunDateTime;
+ private final String previousRunStatus;
+ private final String previousRunErrorLog;
+ private final String previousRunErrorMessage;
+ private final Integer numberOfRuns;
+ private final boolean isActive;
+ private final List emailAttachmentFileFormatOptions;
+ private final List stretchyReportParamDateOptions;
+ private final Long runAsUserId;
+
+ private ReportMailingJobData(final Long id, final String name, final String description, final DateTime startDateTime,
+ final String recurrence, final ReportMailingJobTimelineData timeline, final String emailRecipients, final String emailSubject,
+ final String emailMessage, final EnumOptionData emailAttachmentFileFormat, final ReportData stretchyReport, final String stretchyReportParamMap,
+ final DateTime previousRunDateTime, final DateTime nextRunDateTime, final String previousRunStatus, final String previousRunErrorLog,
+ final String previousRunErrorMessage, final Integer numberOfRuns, final boolean isActive, final List emailAttachmentFileFormatOptions,
+ final List stretchyReportParamDateOptions, final Long runAsUserId) {
+ this.id = id;
+ this.name = name;
+ this.description = description;
+ this.startDateTime = startDateTime;
+ this.recurrence = recurrence;
+ this.timeline = timeline;
+ this.emailRecipients = emailRecipients;
+ this.emailMessage = emailMessage;
+ this.emailSubject = emailSubject;
+ this.emailAttachmentFileFormat = emailAttachmentFileFormat;
+ this.stretchyReport = stretchyReport;
+ this.stretchyReportParamMap = stretchyReportParamMap;
+ this.previousRunDateTime = previousRunDateTime;
+ this.nextRunDateTime = nextRunDateTime;
+ this.previousRunStatus = previousRunStatus;
+ this.previousRunErrorLog = previousRunErrorLog;
+ this.previousRunErrorMessage = previousRunErrorMessage;
+ this.isActive = isActive;
+ this.numberOfRuns = numberOfRuns;
+ this.emailAttachmentFileFormatOptions = emailAttachmentFileFormatOptions;
+ this.stretchyReportParamDateOptions = stretchyReportParamDateOptions;
+ this.runAsUserId = runAsUserId;
+ }
+
+ /**
+ * @return an instance of the ReportMailingJobData class
+ **/
+ public static ReportMailingJobData newInstance(final Long id, final String name, final String description, final DateTime startDateTime,
+ final String recurrence, final ReportMailingJobTimelineData timeline, final String emailRecipients, final String emailSubject,
+ final String emailMessage, final EnumOptionData emailAttachmentFileFormat, final ReportData stretchyReport, final String stretchyReportParamMap,
+ final DateTime previousRunDateTime, final DateTime nextRunDateTime, final String previousRunStatus, final String previousRunErrorLog,
+ final String previousRunErrorMessage, final Integer numberOfRuns, final boolean isActive, final Long runAsUserId) {
+ return new ReportMailingJobData(id, name, description, startDateTime, recurrence, timeline, emailRecipients, emailSubject,
+ emailMessage, emailAttachmentFileFormat, stretchyReport, stretchyReportParamMap, previousRunDateTime, nextRunDateTime,
+ previousRunStatus, previousRunErrorLog, previousRunErrorMessage, numberOfRuns, isActive, null, null, runAsUserId);
+ }
+
+ /**
+ * @return an instance of the ReportMailingJobData class
+ **/
+ public static ReportMailingJobData newInstance(final List emailAttachmentFileFormatOptions,
+ final List stretchyReportParamDateOptions) {
+ return new ReportMailingJobData(null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, false, emailAttachmentFileFormatOptions, stretchyReportParamDateOptions, null);
+ }
+
+ /**
+ * @return an instance of the ReportMailingJobData class
+ **/
+ public static ReportMailingJobData newInstance(final ReportMailingJobData dataWithoutEnumOptions, final ReportMailingJobData dataWithEnumOptions) {
+ return new ReportMailingJobData(dataWithoutEnumOptions.id, dataWithoutEnumOptions.name, dataWithoutEnumOptions.description, dataWithoutEnumOptions.startDateTime,
+ dataWithoutEnumOptions.recurrence, dataWithoutEnumOptions.timeline, dataWithoutEnumOptions.emailRecipients, dataWithoutEnumOptions.emailSubject,
+ dataWithoutEnumOptions.emailMessage, dataWithoutEnumOptions.emailAttachmentFileFormat, dataWithoutEnumOptions.stretchyReport,
+ dataWithoutEnumOptions.stretchyReportParamMap, dataWithoutEnumOptions.previousRunDateTime, dataWithoutEnumOptions.nextRunDateTime,
+ dataWithoutEnumOptions.previousRunStatus, dataWithoutEnumOptions.previousRunErrorLog, dataWithoutEnumOptions.previousRunErrorMessage,
+ dataWithoutEnumOptions.numberOfRuns, dataWithoutEnumOptions.isActive, dataWithEnumOptions.emailAttachmentFileFormatOptions,
+ dataWithEnumOptions.stretchyReportParamDateOptions, dataWithoutEnumOptions.runAsUserId);
+ }
+
+ /**
+ * @return the id
+ */
+ public Long getId() {
+ return id;
+ }
+
+ /**
+ * @return the name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * @return the description
+ */
+ public String getDescription() {
+ return description;
+ }
+
+ /**
+ * @return the startDateTime
+ */
+ public DateTime getStartDateTime() {
+ return startDateTime;
+ }
+
+ /**
+ * @return the recurrence
+ */
+ public String getRecurrence() {
+ return recurrence;
+ }
+
+ /**
+ * @return the timeline
+ */
+ public ReportMailingJobTimelineData getTimeline() {
+ return timeline;
+ }
+
+ /**
+ * @return the emailRecipients
+ */
+ public String getEmailRecipients() {
+ return emailRecipients;
+ }
+
+ /**
+ * @return the emailSubject
+ */
+ public String getEmailSubject() {
+ return emailSubject;
+ }
+
+ /**
+ * @return the emailMessage
+ */
+ public String getEmailMessage() {
+ return emailMessage;
+ }
+
+ /**
+ * @return the emailAttachmentFileFormat
+ */
+ public EnumOptionData getEmailAttachmentFileFormat() {
+ return emailAttachmentFileFormat;
+ }
+
+ /**
+ * @return the stretchyReport
+ */
+ public ReportData getStretchyReport() {
+ return stretchyReport;
+ }
+
+ /**
+ * @return the stretchyReportParamMap
+ */
+ public String getStretchyReportParamMap() {
+ return stretchyReportParamMap;
+ }
+
+ /**
+ * @return the previousRunDateTime
+ */
+ public DateTime getPreviousRunDateTime() {
+ return previousRunDateTime;
+ }
+
+ /**
+ * @return the nextRunDateTime
+ */
+ public DateTime getNextRunDateTime() {
+ return nextRunDateTime;
+ }
+
+ /**
+ * @return the previousRunStatus
+ */
+ public String getPreviousRunStatus() {
+ return previousRunStatus;
+ }
+
+ /**
+ * @return the previousRunErrorLog
+ */
+ public String getPreviousRunErrorLog() {
+ return previousRunErrorLog;
+ }
+
+ /**
+ * @return the previousRunErrorMessage
+ */
+ public String getPreviousRunErrorMessage() {
+ return previousRunErrorMessage;
+ }
+
+ /**
+ * @return the isActive
+ */
+ public boolean isActive() {
+ return isActive;
+ }
+
+ /**
+ * @return the emailAttachmentFileFormatOptions
+ */
+ public List getEmailAttachmentFileFormatOptions() {
+ return emailAttachmentFileFormatOptions;
+ }
+
+ /**
+ * @return the stretchyReportParamDateOptions
+ **/
+ public List getStretchyReportParamDateOptions() {
+ return this.stretchyReportParamDateOptions;
+ }
+
+ /**
+ * @return the numberOfRuns
+ */
+ public Integer getNumberOfRuns() {
+ return numberOfRuns;
+ }
+
+ /**
+ * @return the runAsUserId
+ */
+ public Long getRunAsUserId() {
+ return runAsUserId;
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/data/ReportMailingJobEmailAttachmentFileFormat.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/data/ReportMailingJobEmailAttachmentFileFormat.java
new file mode 100644
index 000000000..c3e15ca81
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/data/ReportMailingJobEmailAttachmentFileFormat.java
@@ -0,0 +1,176 @@
+/**
+ * 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.reportmailingjob.data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+
+public enum ReportMailingJobEmailAttachmentFileFormat {
+ INVALID(0, "ReportMailingJobEmailAttachmentFileFormat.INVALID", "Invalid"),
+ XLS(1, "ReportMailingJobEmailAttachmentFileFormat.XLS", "XLS"),
+ PDF(2, "ReportMailingJobEmailAttachmentFileFormat.PDF", "PDF"),
+ CSV(3, "ReportMailingJobEmailAttachmentFileFormat.CSV", "CSV");
+
+ private String code;
+ private String value;
+ private Integer id;
+
+ private ReportMailingJobEmailAttachmentFileFormat(final Integer id, final String code, final String value) {
+ this.value = value;
+ this.code = code;
+ this.id = id;
+ }
+
+ /**
+ * Creates a new {@link ReportMailingJobEmailAttachmentFileFormat} object
+ *
+ * @param value the value of the enum constant
+ * @return {@link ReportMailingJobEmailAttachmentFileFormat} object
+ */
+ public static ReportMailingJobEmailAttachmentFileFormat newInstance(final String value) {
+ ReportMailingJobEmailAttachmentFileFormat emailAttachmentFileFormat = INVALID;
+
+ if (StringUtils.equalsIgnoreCase(value, XLS.value)) {
+ emailAttachmentFileFormat = XLS;
+ } else if (StringUtils.equalsIgnoreCase(value, PDF.value)) {
+ emailAttachmentFileFormat = PDF;
+ } else if (StringUtils.equalsIgnoreCase(value, CSV.value)) {
+ emailAttachmentFileFormat = CSV;
+ }
+
+ return emailAttachmentFileFormat;
+ }
+
+ /**
+ * Creates a new {@link ReportMailingJobEmailAttachmentFileFormat} object
+ *
+ * @param id the id of the enum constant
+ * @return {@link ReportMailingJobEmailAttachmentFileFormat} object
+ */
+ public static ReportMailingJobEmailAttachmentFileFormat newInstance(final Integer id) {
+ ReportMailingJobEmailAttachmentFileFormat emailAttachmentFileFormat = INVALID;
+
+ if (id == XLS.id) {
+ emailAttachmentFileFormat = XLS;
+ } else if (id == PDF.id) {
+ emailAttachmentFileFormat = PDF;
+ } else if (id == CSV.id) {
+ emailAttachmentFileFormat = CSV;
+ }
+
+ return emailAttachmentFileFormat;
+ }
+
+ /**
+ * @return the code
+ */
+ public String getCode() {
+ return code;
+ }
+
+ /**
+ * @return the value
+ */
+ public String getValue() {
+ return value;
+ }
+
+ /**
+ * @return the id
+ */
+ public Integer getId() {
+ return id;
+ }
+
+ /**
+ * @return true if the enum is equals to "INVALID"
+ */
+ public boolean isInvalid() {
+ return this.equals(INVALID);
+ }
+
+ /**
+ * @return true if the enum is not equals to "INVALID"
+ */
+ public boolean isValid() {
+ return !this.isInvalid();
+ }
+
+ /**
+ * @return list of valid ReportMailingJobEmailAttachmentFileFormat values
+ **/
+ public static Object[] validValues() {
+ List validValues = new ArrayList<>();
+
+ for (ReportMailingJobEmailAttachmentFileFormat constant : ReportMailingJobEmailAttachmentFileFormat.values()) {
+ if (constant.isValid()) {
+ validValues.add(constant.value);
+ }
+ }
+
+ return validValues.toArray();
+ }
+
+ /**
+ * @return list of valid ReportMailingJobEmailAttachmentFileFormat values
+ **/
+ public static Object[] validIds() {
+ List validValues = new ArrayList<>();
+
+ for (ReportMailingJobEmailAttachmentFileFormat constant : ReportMailingJobEmailAttachmentFileFormat.values()) {
+ if (constant.isValid()) {
+ validValues.add(constant.id);
+ }
+ }
+
+ return validValues.toArray();
+ }
+
+ /**
+ * get a {@link EnumOptionData} representation of the {@link ReportMailingJobEmailAttachmentFileFormat} object
+ *
+ * @return {@link EnumOptionData} object
+ */
+ public EnumOptionData toEnumOptionData() {
+ // get the long value of the enum id
+ final Long id = (this.id != null) ? this.id.longValue() : null;
+
+ return new EnumOptionData(id, code, value);
+ }
+
+ /**
+ * get the {@link EnumOptionData} representation of all valid {@link ReportMailingJobEmailAttachmentFileFormat} objects
+ *
+ * @return List of {@link EnumOptionData} objects
+ */
+ public static List validOptions() {
+ List options = new ArrayList<>();
+
+ for (ReportMailingJobEmailAttachmentFileFormat constant : ReportMailingJobEmailAttachmentFileFormat.values()) {
+ if (constant.isValid()) {
+ options.add(constant.toEnumOptionData());
+ }
+ }
+
+ return options;
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/data/ReportMailingJobEmailData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/data/ReportMailingJobEmailData.java
new file mode 100644
index 000000000..dda7e2df3
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/data/ReportMailingJobEmailData.java
@@ -0,0 +1,66 @@
+/**
+ * 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.reportmailingjob.data;
+
+import java.io.File;
+
+/**
+ * Immutable data object representing report mailing job email data.
+ **/
+public class ReportMailingJobEmailData {
+ private final String to;
+ private final String text;
+ private final String subject;
+ private final File attachment;
+
+ public ReportMailingJobEmailData(final String to, final String text, final String subject, final File attachment) {
+ this.to = to;
+ this.text = text;
+ this.subject = subject;
+ this.attachment = attachment;
+ }
+
+ /**
+ * @return the to
+ */
+ public String getTo() {
+ return to;
+ }
+
+ /**
+ * @return the text
+ */
+ public String getText() {
+ return text;
+ }
+
+ /**
+ * @return the subject
+ */
+ public String getSubject() {
+ return subject;
+ }
+
+ /**
+ * @return the attachment
+ */
+ public File getAttachment() {
+ return attachment;
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/data/ReportMailingJobPreviousRunStatus.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/data/ReportMailingJobPreviousRunStatus.java
new file mode 100644
index 000000000..3f49cd994
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/data/ReportMailingJobPreviousRunStatus.java
@@ -0,0 +1,90 @@
+/**
+ * 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.reportmailingjob.data;
+
+import org.apache.commons.lang.StringUtils;
+
+public enum ReportMailingJobPreviousRunStatus {
+ INVALID(-1, "ReportMailingJobPreviousRunStatus.INVALID", "Invalid"),
+ SUCCESS(1, "ReportMailingJobPreviousRunStatus.SUCCESS", "Success"),
+ ERROR(0, "ReportMailingJobPreviousRunStatus.ERROR", "Error");
+
+ private final String code;
+ private final String value;
+ private final Integer id;
+
+ private ReportMailingJobPreviousRunStatus(final Integer id, final String code, final String value) {
+ this.value = value;
+ this.code = code;
+ this.id = id;
+ }
+
+ /**
+ * Creates a {@link ReportMailingJobPreviousRunStatus} object
+ *
+ * @param value the value of the enum constant
+ * @return {@link ReportMailingJobPreviousRunStatus} object
+ */
+ public static ReportMailingJobPreviousRunStatus newInstance(final String value) {
+ ReportMailingJobPreviousRunStatus previousRunStatus = INVALID;
+
+ if (StringUtils.equalsIgnoreCase(value, SUCCESS.value)) {
+ previousRunStatus = SUCCESS;
+ } else if (StringUtils.equalsIgnoreCase(value, ERROR.value)) {
+ previousRunStatus = ERROR;
+ }
+
+ return previousRunStatus;
+ }
+
+ /**
+ * @return the code
+ */
+ public String getCode() {
+ return code;
+ }
+
+ /**
+ * @return the value
+ */
+ public String getValue() {
+ return value;
+ }
+
+ /**
+ * @return the id
+ */
+ public Integer getId() {
+ return id;
+ }
+
+ /**
+ * @return true/false
+ **/
+ public boolean isSuccess() {
+ return this.value.equals(SUCCESS.getValue());
+ }
+
+ /**
+ * @return boolean true/false
+ **/
+ public boolean isError() {
+ return this.value.equals(ERROR.getValue());
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/data/ReportMailingJobRunHistoryData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/data/ReportMailingJobRunHistoryData.java
new file mode 100644
index 000000000..0642f7b0e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/data/ReportMailingJobRunHistoryData.java
@@ -0,0 +1,107 @@
+/**
+ * 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.reportmailingjob.data;
+
+import org.joda.time.DateTime;
+
+/**
+ * Immutable data object representing report mailing job run history data.
+ **/
+public class ReportMailingJobRunHistoryData {
+ private final Long id;
+ private final Long reportMailingJobId;
+ private final DateTime startDateTime;
+ private final DateTime endDateTime;
+ private final String status;
+ private final String errorMessage;
+ private final String errorLog;
+
+ /**
+ * ReportMailingJobRunHistoryData private constructor
+ **/
+ private ReportMailingJobRunHistoryData(Long id, Long reportMailingJobId, DateTime startDateTime,
+ DateTime endDateTime, String status, String errorMessage, String errorLog) {
+ this.id = id;
+ this.reportMailingJobId = reportMailingJobId;
+ this.startDateTime = startDateTime;
+ this.endDateTime = endDateTime;
+ this.status = status;
+ this.errorMessage = errorMessage;
+ this.errorLog = errorLog;
+ }
+
+ /**
+ * creates an instance of the ReportMailingJobRunHistoryData class
+ *
+ * @return ReportMailingJobRunHistoryData object
+ **/
+ public static ReportMailingJobRunHistoryData newInstance(Long id, Long reportMailingJobId, DateTime startDateTime,
+ DateTime endDateTime, String status, String errorMessage, String errorLog) {
+ return new ReportMailingJobRunHistoryData(id, reportMailingJobId, startDateTime, endDateTime, status, errorMessage, errorLog);
+ }
+
+ /**
+ * @return the id
+ */
+ public Long getId() {
+ return id;
+ }
+
+ /**
+ * @return the reportMailingJobId
+ */
+ public Long getReportMailingJobId() {
+ return reportMailingJobId;
+ }
+
+ /**
+ * @return the startDateTime
+ */
+ public DateTime getStartDateTime() {
+ return startDateTime;
+ }
+
+ /**
+ * @return the endDateTime
+ */
+ public DateTime getEndDateTime() {
+ return endDateTime;
+ }
+
+ /**
+ * @return the status
+ */
+ public String getStatus() {
+ return status;
+ }
+
+ /**
+ * @return the errorMessage
+ */
+ public String getErrorMessage() {
+ return errorMessage;
+ }
+
+ /**
+ * @return the errorLog
+ */
+ public String getErrorLog() {
+ return errorLog;
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/data/ReportMailingJobStretchyReportParamDateOption.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/data/ReportMailingJobStretchyReportParamDateOption.java
new file mode 100644
index 000000000..01cf80847
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/data/ReportMailingJobStretchyReportParamDateOption.java
@@ -0,0 +1,166 @@
+/**
+ * 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.reportmailingjob.data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+
+public enum ReportMailingJobStretchyReportParamDateOption {
+ INVALID(0, "ReportMailingJobStretchyReportParamDateOption.INVALID", "Invalid"),
+ TODAY(1, "ReportMailingJobStretchyReportParamDateOption.TODAY", "Today"),
+ YESTERDAY(2, "ReportMailingJobStretchyReportParamDateOption.YESTERDAY", "Yesterday"),
+ TOMORROW(3, "ReportMailingJobStretchyReportParamDateOption.TOMORROW", "Tomorrow");
+
+ private String code;
+ private String value;
+ private Integer id;
+
+ /**
+ * @param id
+ * @param code
+ * @param value
+ */
+ private ReportMailingJobStretchyReportParamDateOption(final Integer id, final String code, final String value) {
+ this.value = value;
+ this.code = code;
+ this.id = id;
+ }
+
+ /**
+ * Creates a new {@link ReportMailingJobStretchyReportParamDateOption} object
+ *
+ * @param value value of the enum constant
+ * @return {@link ReportMailingJobStretchyReportParamDateOption} object
+ */
+ public static ReportMailingJobStretchyReportParamDateOption newInstance(final String value) {
+ ReportMailingJobStretchyReportParamDateOption reportMailingJobStretchyReportParamDateOption = INVALID;
+
+ if (StringUtils.equalsIgnoreCase(value, TODAY.value)) {
+ reportMailingJobStretchyReportParamDateOption = TODAY;
+ } else if (StringUtils.equalsIgnoreCase(value, YESTERDAY.value)) {
+ reportMailingJobStretchyReportParamDateOption = YESTERDAY;
+ } else if (StringUtils.equalsIgnoreCase(value, TOMORROW.value)) {
+ reportMailingJobStretchyReportParamDateOption = TOMORROW;
+ }
+
+ return reportMailingJobStretchyReportParamDateOption;
+ }
+
+ /**
+ * Creates a new {@link ReportMailingJobStretchyReportParamDateOption} object
+ *
+ * @param id id of the enum constant
+ * @return {@link ReportMailingJobStretchyReportParamDateOption} object
+ */
+ public static ReportMailingJobStretchyReportParamDateOption newInstance(final Integer id) {
+ ReportMailingJobStretchyReportParamDateOption reportMailingJobStretchyReportParamDateOption = INVALID;
+
+ if (id == TODAY.id) {
+ reportMailingJobStretchyReportParamDateOption = TODAY;
+ } else if (id == YESTERDAY.id) {
+ reportMailingJobStretchyReportParamDateOption = YESTERDAY;
+ } else if (id == TOMORROW.id) {
+ reportMailingJobStretchyReportParamDateOption = TOMORROW;
+ }
+
+ return reportMailingJobStretchyReportParamDateOption;
+ }
+
+ /**
+ * @return the code
+ */
+ public String getCode() {
+ return code;
+ }
+
+ /**
+ * @return the value
+ */
+ public String getValue() {
+ return value;
+ }
+
+ /**
+ * @return the id
+ */
+ public Integer getId() {
+ return id;
+ }
+
+ /**
+ * @return true if the enum is equals to "INVALID"
+ */
+ public boolean isInvalid() {
+ return this.equals(INVALID);
+ }
+
+ /**
+ * @return true if the enum is not equals to "INVALID"
+ */
+ public boolean isValid() {
+ return !this.isInvalid();
+ }
+
+ /**
+ * @return list of valid ReportMailingJobStretchyReportParamDateOption values
+ **/
+ public static Object[] validValues() {
+ List validValues = new ArrayList<>();
+
+ for (ReportMailingJobStretchyReportParamDateOption constant : ReportMailingJobStretchyReportParamDateOption.values()) {
+ if (constant.isValid()) {
+ validValues.add(constant.value);
+ }
+ }
+
+ return validValues.toArray();
+ }
+
+ /**
+ * get a {@link EnumOptionData} representation of the {@link ReportMailingJobStretchyReportParamDateOption} object
+ *
+ * @return {@link EnumOptionData} object
+ */
+ public EnumOptionData toEnumOptionData() {
+ // get the long value of the enum id
+ final Long id = (this.id != null) ? this.id.longValue() : null;
+
+ return new EnumOptionData(id, code, value);
+ }
+
+ /**
+ * get the {@link EnumOptionData} representation of all valid {@link ReportMailingJobStretchyReportParamDateOption} objects
+ *
+ * @return List of {@link EnumOptionData} objects
+ */
+ public static List validOptions() {
+ List options = new ArrayList<>();
+
+ for (ReportMailingJobStretchyReportParamDateOption constant : ReportMailingJobStretchyReportParamDateOption.values()) {
+ if (constant.isValid()) {
+ options.add(constant.toEnumOptionData());
+ }
+ }
+
+ return options;
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/data/ReportMailingJobTimelineData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/data/ReportMailingJobTimelineData.java
new file mode 100644
index 000000000..032009762
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/data/ReportMailingJobTimelineData.java
@@ -0,0 +1,59 @@
+/**
+ * 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.reportmailingjob.data;
+
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable data object represent the timeline events of a report mailing job (creation)
+ **/
+@SuppressWarnings("unused")
+public class ReportMailingJobTimelineData {
+ private final LocalDate createdOnDate;
+ private final String createdByUsername;
+ private final String createdByFirstname;
+ private final String createdByLastname;
+ private final LocalDate updatedOnDate;
+ private final String updatedByUsername;
+ private final String updatedByFirstname;
+ private final String updatedByLastname;
+
+ /**
+ * @param createdOnDate
+ * @param createdByUsername
+ * @param createdByFirstname
+ * @param createdByLastname
+ * @param updatedOnDate
+ * @param updatedByUsername
+ * @param updatedByFirstname
+ * @param updatedByLastname
+ */
+ public ReportMailingJobTimelineData(LocalDate createdOnDate, String createdByUsername, String createdByFirstname,
+ String createdByLastname, LocalDate updatedOnDate, String updatedByUsername, String updatedByFirstname,
+ String updatedByLastname) {
+ this.createdOnDate = createdOnDate;
+ this.createdByUsername = createdByUsername;
+ this.createdByFirstname = createdByFirstname;
+ this.createdByLastname = createdByLastname;
+ this.updatedOnDate = updatedOnDate;
+ this.updatedByUsername = updatedByUsername;
+ this.updatedByFirstname = updatedByFirstname;
+ this.updatedByLastname = updatedByLastname;
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/domain/ReportMailingJob.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/domain/ReportMailingJob.java
new file mode 100644
index 000000000..c7f3bb7d4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/domain/ReportMailingJob.java
@@ -0,0 +1,558 @@
+/**
+ * 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.reportmailingjob.domain;
+
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import javax.persistence.UniqueConstraint;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
+import org.apache.fineract.infrastructure.dataqueries.domain.Report;
+import org.apache.fineract.infrastructure.reportmailingjob.ReportMailingJobConstants;
+import org.apache.fineract.infrastructure.reportmailingjob.data.ReportMailingJobEmailAttachmentFileFormat;
+import org.apache.fineract.infrastructure.reportmailingjob.data.ReportMailingJobPreviousRunStatus;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.DateTime;
+import org.joda.time.LocalDateTime;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+
+@Entity
+@Table(name = "m_report_mailing_job", uniqueConstraints = { @UniqueConstraint(columnNames = { "name" }, name = "unique_name") })
+public class ReportMailingJob extends AbstractAuditableCustom {
+ private static final long serialVersionUID = -2197602941230009227L;
+
+ @Column(name = "name", nullable = false)
+ private String name;
+
+ @Column(name = "description", nullable = true)
+ private String description;
+
+ @Column(name = "start_datetime", nullable = false)
+ @Temporal(TemporalType.TIMESTAMP)
+ private Date startDateTime;
+
+ @Column(name = "recurrence", nullable = true)
+ private String recurrence;
+
+ @Column(name = "email_recipients", nullable = false)
+ private String emailRecipients;
+
+ @Column(name = "email_subject", nullable = false)
+ private String emailSubject;
+
+ @Column(name = "email_message", nullable = false)
+ private String emailMessage;
+
+ @Column(name = "email_attachment_file_format", nullable = false)
+ private String emailAttachmentFileFormat;
+
+ @ManyToOne
+ @JoinColumn(name = "stretchy_report_id", nullable = false)
+ private Report stretchyReport;
+
+ @Column(name = "stretchy_report_param_map", nullable = true)
+ private String stretchyReportParamMap;
+
+ @Column(name = "previous_run_datetime", nullable = true)
+ @Temporal(TemporalType.TIMESTAMP)
+ private Date previousRunDateTime;
+
+ @Column(name = "next_run_datetime", nullable = true)
+ @Temporal(TemporalType.TIMESTAMP)
+ private Date nextRunDateTime;
+
+ @Column(name = "previous_run_status", nullable = true)
+ private String previousRunStatus;
+
+ @Column(name = "previous_run_error_log", nullable = true)
+ private String previousRunErrorLog;
+
+ @Column(name = "previous_run_error_message", nullable = true)
+ private String previousRunErrorMessage;
+
+ @Column(name = "number_of_runs", nullable = false)
+ private Integer numberOfRuns;
+
+ @Column(name = "is_active", nullable = false)
+ private boolean isActive;
+
+ @Column(name = "is_deleted", nullable = false)
+ private boolean isDeleted;
+
+ @ManyToOne(optional = false)
+ @JoinColumn(name = "run_as_userid", nullable = false)
+ private AppUser runAsUser;
+
+ /**
+ * ReportMailingJob protected constructor
+ **/
+ protected ReportMailingJob() { }
+
+ /**
+ * ReportMailingJob private constructor
+ **/
+ private ReportMailingJob(final String name, final String description, final LocalDateTime startDateTime,
+ final String recurrence, final String emailRecipients, final String emailSubject,
+ final String emailMessage, final ReportMailingJobEmailAttachmentFileFormat emailAttachmentFileFormat,
+ final Report stretchyReport, final String stretchyReportParamMap, final LocalDateTime previousRunDateTime, final LocalDateTime nextRunDateTime,
+ final ReportMailingJobPreviousRunStatus previousRunStatus, final String previousRunErrorLog, final String previousRunErrorMessage,
+ final boolean isActive, final boolean isDeleted, final AppUser runAsUser) {
+ this.name = name;
+ this.description = description;
+ this.startDateTime = null;
+
+ if (startDateTime != null) {
+ this.startDateTime = startDateTime.toDate();
+ }
+
+ this.recurrence = recurrence;
+ this.emailRecipients = emailRecipients;
+ this.emailSubject = emailSubject;
+ this.emailMessage = emailMessage;
+ this.emailAttachmentFileFormat = emailAttachmentFileFormat.getValue();
+ this.stretchyReport = stretchyReport;
+ this.stretchyReportParamMap = stretchyReportParamMap;
+ this.previousRunDateTime = null;
+
+ if (previousRunDateTime != null) {
+ this.previousRunDateTime = previousRunDateTime.toDate();
+ }
+
+ this.nextRunDateTime = null;
+
+ if (nextRunDateTime != null) {
+ this.nextRunDateTime = nextRunDateTime.toDate();
+ }
+
+ this.previousRunStatus = null;
+
+ if (previousRunStatus != null) {
+ this.previousRunStatus = previousRunStatus.getValue();
+ }
+
+ if (numberOfRuns == null) {
+ this.numberOfRuns = 0;
+ }
+
+ this.previousRunErrorLog = previousRunErrorLog;
+ this.previousRunErrorMessage = previousRunErrorMessage;
+ this.isActive = isActive;
+ this.isDeleted = isDeleted;
+ this.runAsUser = runAsUser;
+ }
+
+ /**
+ * create a new instance of the ReportMailingJob for a new entry
+ *
+ * @return ReportMailingJob object
+ **/
+ public static ReportMailingJob newInstance(final String name, final String description, final LocalDateTime startDateTime, final String recurrence,
+ final String emailRecipients, final String emailSubject, final String emailMessage,
+ final ReportMailingJobEmailAttachmentFileFormat emailAttachmentFileFormat, final Report stretchyReport, final String stretchyReportParamMap,
+ final boolean isActive, final AppUser runAsUser) {
+ return new ReportMailingJob(name, description, startDateTime, recurrence, emailRecipients, emailSubject,
+ emailMessage, emailAttachmentFileFormat, stretchyReport, stretchyReportParamMap, null, null, null, null, null, isActive, false, runAsUser);
+ }
+
+ /**
+ * create a new instance of the ReportmailingJob for a new entry
+ *
+ * @return ReportMailingJob object
+ **/
+ public static ReportMailingJob newInstance(JsonCommand jsonCommand, final Report stretchyReport,
+ final AppUser runAsUser) {
+ final String name = jsonCommand.stringValueOfParameterNamed(ReportMailingJobConstants.NAME_PARAM_NAME);
+ final String description = jsonCommand.stringValueOfParameterNamed(ReportMailingJobConstants.DESCRIPTION_PARAM_NAME);
+ final String recurrence = jsonCommand.stringValueOfParameterNamed(ReportMailingJobConstants.RECURRENCE_PARAM_NAME);
+ final boolean isActive = jsonCommand.booleanPrimitiveValueOfParameterNamed(ReportMailingJobConstants.IS_ACTIVE_PARAM_NAME);
+ final String emailRecipients = jsonCommand.stringValueOfParameterNamed(ReportMailingJobConstants.EMAIL_RECIPIENTS_PARAM_NAME);
+ final String emailSubject = jsonCommand.stringValueOfParameterNamed(ReportMailingJobConstants.EMAIL_SUBJECT_PARAM_NAME);
+ final String emailMessage = jsonCommand.stringValueOfParameterNamed(ReportMailingJobConstants.EMAIL_MESSAGE_PARAM_NAME);
+ final String stretchyReportParamMap = jsonCommand.stringValueOfParameterNamed(ReportMailingJobConstants.STRETCHY_REPORT_PARAM_MAP_PARAM_NAME);
+ final Integer emailAttachmentFileFormatId = jsonCommand.integerValueOfParameterNamed(ReportMailingJobConstants.EMAIL_ATTACHMENT_FILE_FORMAT_ID_PARAM_NAME);
+ final ReportMailingJobEmailAttachmentFileFormat emailAttachmentFileFormat = ReportMailingJobEmailAttachmentFileFormat.newInstance(emailAttachmentFileFormatId);
+ LocalDateTime startDateTime = new LocalDateTime();
+
+ if (jsonCommand.hasParameter(ReportMailingJobConstants.START_DATE_TIME_PARAM_NAME)) {
+ final String startDateTimeString = jsonCommand.stringValueOfParameterNamed(ReportMailingJobConstants.START_DATE_TIME_PARAM_NAME);
+
+ if (startDateTimeString != null) {
+ final DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(jsonCommand.dateFormat()).withLocale(jsonCommand.extractLocale());
+ startDateTime = LocalDateTime.parse(startDateTimeString, dateTimeFormatter);
+ }
+ }
+
+ return new ReportMailingJob(name, description, startDateTime, recurrence, emailRecipients, emailSubject,
+ emailMessage, emailAttachmentFileFormat, stretchyReport, stretchyReportParamMap, null, startDateTime, null, null, null, isActive, false, runAsUser);
+ }
+
+ /**
+ * Update the ReportMailingJob entity
+ *
+ * @param jsonCommand JsonCommand object
+ * @return map of string to object
+ **/
+ public Map update(final JsonCommand jsonCommand) {
+ final Map actualChanges = new LinkedHashMap<>();
+
+ if (jsonCommand.isChangeInStringParameterNamed(ReportMailingJobConstants.NAME_PARAM_NAME, this.name)) {
+ final String name = jsonCommand.stringValueOfParameterNamed(ReportMailingJobConstants.NAME_PARAM_NAME);
+ actualChanges.put(ReportMailingJobConstants.NAME_PARAM_NAME, name);
+
+ this.name = name;
+ }
+
+ if (jsonCommand.isChangeInStringParameterNamed(ReportMailingJobConstants.DESCRIPTION_PARAM_NAME, this.description)) {
+ final String description = jsonCommand.stringValueOfParameterNamed(ReportMailingJobConstants.DESCRIPTION_PARAM_NAME);
+ actualChanges.put(ReportMailingJobConstants.DESCRIPTION_PARAM_NAME, description);
+
+ this.description = description;
+ }
+
+ if (jsonCommand.isChangeInStringParameterNamed(ReportMailingJobConstants.RECURRENCE_PARAM_NAME, this.recurrence)) {
+ final String recurrence = jsonCommand.stringValueOfParameterNamed(ReportMailingJobConstants.RECURRENCE_PARAM_NAME);
+ actualChanges.put(ReportMailingJobConstants.RECURRENCE_PARAM_NAME, recurrence);
+
+ this.recurrence = recurrence;
+ }
+
+ if (jsonCommand.isChangeInBooleanParameterNamed(ReportMailingJobConstants.IS_ACTIVE_PARAM_NAME, this.isActive)) {
+ final boolean isActive = jsonCommand.booleanPrimitiveValueOfParameterNamed(ReportMailingJobConstants.IS_ACTIVE_PARAM_NAME);
+ actualChanges.put(ReportMailingJobConstants.IS_ACTIVE_PARAM_NAME, isActive);
+
+ this.isActive = isActive;
+ }
+
+ if (jsonCommand.isChangeInStringParameterNamed(ReportMailingJobConstants.EMAIL_RECIPIENTS_PARAM_NAME, this.emailRecipients)) {
+ final String emailRecipients = jsonCommand.stringValueOfParameterNamed(ReportMailingJobConstants.EMAIL_RECIPIENTS_PARAM_NAME);
+ actualChanges.put(ReportMailingJobConstants.EMAIL_RECIPIENTS_PARAM_NAME, emailRecipients);
+
+ this.emailRecipients = emailRecipients;
+ }
+
+ if (jsonCommand.isChangeInStringParameterNamed(ReportMailingJobConstants.EMAIL_SUBJECT_PARAM_NAME, this.emailSubject)) {
+ final String emailSubject = jsonCommand.stringValueOfParameterNamed(ReportMailingJobConstants.EMAIL_SUBJECT_PARAM_NAME);
+ actualChanges.put(ReportMailingJobConstants.EMAIL_SUBJECT_PARAM_NAME, emailSubject);
+
+ this.emailSubject = emailSubject;
+ }
+
+ if (jsonCommand.isChangeInStringParameterNamed(ReportMailingJobConstants.EMAIL_MESSAGE_PARAM_NAME, this.emailMessage)) {
+ final String emailMessage = jsonCommand.stringValueOfParameterNamed(ReportMailingJobConstants.EMAIL_MESSAGE_PARAM_NAME);
+ actualChanges.put(ReportMailingJobConstants.EMAIL_MESSAGE_PARAM_NAME, emailMessage);
+
+ this.emailMessage = emailMessage;
+ }
+
+ if (jsonCommand.isChangeInStringParameterNamed(ReportMailingJobConstants.STRETCHY_REPORT_PARAM_MAP_PARAM_NAME, this.stretchyReportParamMap)) {
+ final String stretchyReportParamMap = jsonCommand.stringValueOfParameterNamed(ReportMailingJobConstants.STRETCHY_REPORT_PARAM_MAP_PARAM_NAME);
+ actualChanges.put(ReportMailingJobConstants.STRETCHY_REPORT_PARAM_MAP_PARAM_NAME, stretchyReportParamMap);
+
+ this.stretchyReportParamMap = stretchyReportParamMap;
+ }
+
+ final ReportMailingJobEmailAttachmentFileFormat emailAttachmentFileFormat = ReportMailingJobEmailAttachmentFileFormat.newInstance(this.emailAttachmentFileFormat);
+
+ if (jsonCommand.isChangeInIntegerParameterNamed(ReportMailingJobConstants.EMAIL_ATTACHMENT_FILE_FORMAT_ID_PARAM_NAME, emailAttachmentFileFormat.getId())) {
+ final Integer emailAttachmentFileFormatId = jsonCommand.integerValueOfParameterNamed(ReportMailingJobConstants.EMAIL_ATTACHMENT_FILE_FORMAT_ID_PARAM_NAME);
+ actualChanges.put(ReportMailingJobConstants.EMAIL_ATTACHMENT_FILE_FORMAT_ID_PARAM_NAME, emailAttachmentFileFormatId);
+
+ final ReportMailingJobEmailAttachmentFileFormat newEmailAttachmentFileFormat = ReportMailingJobEmailAttachmentFileFormat.newInstance(emailAttachmentFileFormatId);
+ this.emailAttachmentFileFormat = newEmailAttachmentFileFormat.getValue();
+ }
+
+ final String newStartDateTimeString = jsonCommand.stringValueOfParameterNamed(ReportMailingJobConstants.START_DATE_TIME_PARAM_NAME);
+
+ if (!StringUtils.isEmpty(newStartDateTimeString)) {
+ final DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(jsonCommand.dateFormat()).withLocale(jsonCommand.extractLocale());
+ final LocalDateTime newStartDateTime = LocalDateTime.parse(newStartDateTimeString, dateTimeFormatter);
+ final LocalDateTime oldStartDateTime = (this.startDateTime != null) ? new LocalDateTime(this.startDateTime) : null;
+
+ if ((oldStartDateTime != null) && !newStartDateTime.equals(oldStartDateTime)) {
+ actualChanges.put(ReportMailingJobConstants.START_DATE_TIME_PARAM_NAME, newStartDateTimeString);
+
+ this.startDateTime = newStartDateTime.toDate();
+ }
+ }
+
+ Long currentStretchyReportId = null;
+
+ if (this.stretchyReport != null) {
+ currentStretchyReportId = this.stretchyReport.getId();
+ }
+
+ if (jsonCommand.isChangeInLongParameterNamed(ReportMailingJobConstants.STRETCHY_REPORT_ID_PARAM_NAME, currentStretchyReportId)) {
+ final Long updatedStretchyReportId = jsonCommand.longValueOfParameterNamed(
+ ReportMailingJobConstants.STRETCHY_REPORT_ID_PARAM_NAME);
+ actualChanges.put(ReportMailingJobConstants.STRETCHY_REPORT_ID_PARAM_NAME, updatedStretchyReportId);
+ }
+
+ return actualChanges;
+ }
+
+ /**
+ * update the stretchy report entity associated with the credit check
+ *
+ * @param stretchyReport -- Report entity
+ * @return None
+ **/
+ public void update(final Report stretchyReport) {
+ if (stretchyReport != null) {
+ this.stretchyReport = stretchyReport;
+ }
+ }
+
+ /**
+ * delete the report mailing job, set the isDeleted property to 1 and alter the name
+ *
+ * @return None
+ **/
+ public void delete() {
+ this.isDeleted = true;
+ this.isActive = false;
+ this.name = this.name + "_deleted_" + this.getId();
+ }
+
+ /**
+ * @return the value of the name property
+ **/
+ public String getName() {
+ return this.name;
+ }
+
+ /**
+ * @return the value of the description property
+ **/
+ public String getDescription() {
+ return this.description;
+ }
+
+ /**
+ * @return the value of the startDateTime property
+ **/
+ public DateTime getStartDateTime() {
+ return (this.startDateTime != null) ? new DateTime(this.startDateTime) : null;
+ }
+
+ /**
+ * @return the value of the recurrence property
+ **/
+ public String getRecurrence() {
+ return this.recurrence;
+ }
+
+ /**
+ * @return value of the isDeleted property
+ **/
+ public boolean isDeleted() {
+ return this.isDeleted;
+ }
+
+ /**
+ * @return boolean true if isDeleted property equals 0, else false
+ **/
+ public boolean isNotDeleted() {
+ return !this.isDeleted;
+ }
+
+ /**
+ * @return the value of the isActive property
+ **/
+ public boolean isActive() {
+ return this.isActive;
+ }
+
+ /**
+ * @return boolean true if isActive property equals 0, else false
+ **/
+ public boolean isNotActive() {
+ return !this.isActive;
+ }
+
+ /**
+ * @return the emailRecipients
+ */
+ public String getEmailRecipients() {
+ return emailRecipients;
+ }
+
+ /**
+ * @return the emailSubject
+ */
+ public String getEmailSubject() {
+ return emailSubject;
+ }
+
+ /**
+ * @return the emailMessage
+ */
+ public String getEmailMessage() {
+ return emailMessage;
+ }
+
+ /**
+ * @return the emailAttachmentFileFormat
+ */
+ public String getEmailAttachmentFileFormat() {
+ return emailAttachmentFileFormat;
+ }
+
+ /**
+ * @return the stretchyReport
+ */
+ public Report getStretchyReport() {
+ return stretchyReport;
+ }
+
+ /**
+ * @return the stretchyReportParamMap
+ */
+ public String getStretchyReportParamMap() {
+ return stretchyReportParamMap;
+ }
+
+ /**
+ * @return the previousRunDateTime
+ */
+ public DateTime getPreviousRunDateTime() {
+ return (this.previousRunDateTime != null) ? new DateTime(this.previousRunDateTime) : null;
+ }
+
+ /**
+ * @return the nextRunDateTime
+ */
+ public DateTime getNextRunDateTime() {
+ return (this.nextRunDateTime != null) ? new DateTime(this.nextRunDateTime) : null;
+ }
+
+ /**
+ * @return the previousRunStatus
+ */
+ public String getPreviousRunStatus() {
+ return previousRunStatus;
+ }
+
+ /**
+ * @return the previousRunErrorLog
+ */
+ public String getPreviousRunErrorLog() {
+ return previousRunErrorLog;
+ }
+
+ /**
+ * @return the previousRunErrorMessage
+ */
+ public String getPreviousRunErrorMessage() {
+ return previousRunErrorMessage;
+ }
+
+ /**
+ * @return the numberOfRuns
+ */
+ public Integer getNumberOfRuns() {
+ return numberOfRuns;
+ }
+
+ /**
+ * @return the runAsUser
+ */
+ public AppUser getRunAsUser() {
+ return runAsUser;
+ }
+
+ /**
+ * increase the numberOfRuns by 1
+ *
+ * @return None
+ **/
+ public void increaseNumberOfRunsByOne() {
+ this.numberOfRuns++;
+ }
+
+ /**
+ * update the previousRunStatus
+ *
+ * @param previousRunStatus -- the status of the previous job execution
+ * @return None
+ **/
+ public void updatePreviousRunStatus(final String previousRunStatus) {
+ if (!StringUtils.isEmpty(previousRunStatus)) {
+ this.previousRunStatus = previousRunStatus;
+ }
+ }
+
+ /**
+ * update the previousRunDateTime
+ *
+ * @param previousRunDateTime -- previous run date
+ * @return None
+ **/
+ public void updatePreviousRunDateTime(final DateTime previousRunDateTime) {
+ if (previousRunDateTime != null) {
+ this.previousRunDateTime = previousRunDateTime.toDate();
+ }
+ }
+
+ /**
+ * update the nextRunDateTime
+ *
+ * @param nextRunDateTime -- the next run DateTime
+ * @return None
+ **/
+ public void updateNextRunDateTime(final DateTime nextRunDateTime) {
+ if (nextRunDateTime != null) {
+ this.nextRunDateTime = nextRunDateTime.toDate();
+ }
+
+ else {
+ this.nextRunDateTime = null;
+ }
+ }
+
+ /**
+ * deactivate the report mailing job by setting the isActive property to 0
+ *
+ * @return None
+ **/
+ public void deactivate() {
+ this.isActive = false;
+ }
+
+ /**
+ * update the previousRunErrorLog property
+ *
+ * @param previousRunErrorLog -- the previous job run error log
+ * @return None
+ **/
+ public void updatePreviousRunErrorLog(final String previousRunErrorLog) {
+ this.previousRunErrorLog = previousRunErrorLog;
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/domain/ReportMailingJobConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/domain/ReportMailingJobConfiguration.java
new file mode 100644
index 000000000..17eb7b68c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/domain/ReportMailingJobConfiguration.java
@@ -0,0 +1,74 @@
+/**
+ * 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.reportmailingjob.domain;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_report_mailing_job_configuration", uniqueConstraints = { @UniqueConstraint(columnNames = { "name" }, name = "unique_name") })
+public class ReportMailingJobConfiguration extends AbstractPersistable {
+ private static final long serialVersionUID = 3099279770861263184L;
+
+ @Column(name = "name", nullable = false)
+ private String name;
+
+ @Column(name = "value", nullable = false)
+ private String value;
+
+ /**
+ * ReportMailingJobConfiguration protected constructor
+ **/
+ protected ReportMailingJobConfiguration() { }
+
+ /**
+ * ReportMailingJobConfiguration private constructor
+ **/
+ private ReportMailingJobConfiguration(final String name, final String value) {
+ this.name = name;
+ this.value = value;
+ }
+
+ /**
+ * creates an instance of the ReportMailingJobConfiguration class
+ *
+ * @return ReportMailingJobConfiguration object
+ **/
+ public static ReportMailingJobConfiguration newInstance(final String name, final String value) {
+ return new ReportMailingJobConfiguration(name, value);
+ }
+
+ /**
+ * @return the name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * @return the value
+ */
+ public String getValue() {
+ return value;
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/domain/ReportMailingJobConfigurationRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/domain/ReportMailingJobConfigurationRepository.java
new file mode 100644
index 000000000..8e7168ede
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/domain/ReportMailingJobConfigurationRepository.java
@@ -0,0 +1,31 @@
+/**
+ * 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.reportmailingjob.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface ReportMailingJobConfigurationRepository extends JpaRepository,
+ JpaSpecificationExecutor {
+
+ /**
+ * find ReportMailingJobConfiguration by name
+ **/
+ ReportMailingJobConfiguration findByName(final String name);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/domain/ReportMailingJobRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/domain/ReportMailingJobRepository.java
new file mode 100644
index 000000000..94268d837
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/domain/ReportMailingJobRepository.java
@@ -0,0 +1,33 @@
+/**
+ * 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.reportmailingjob.domain;
+
+import java.util.Collection;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface ReportMailingJobRepository extends JpaRepository, JpaSpecificationExecutor {
+ /**
+ * Returns all instances of {@link ReportMailingJob} where isActive is true and isDeleted is false
+ *
+ * @return instances of {@link ReportMailingJob}
+ */
+ public Collection findByIsActiveTrueAndIsDeletedFalse();
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/domain/ReportMailingJobRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/domain/ReportMailingJobRepositoryWrapper.java
new file mode 100644
index 000000000..ad9e84ec6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/domain/ReportMailingJobRepositoryWrapper.java
@@ -0,0 +1,56 @@
+/**
+ * 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.reportmailingjob.domain;
+
+import org.apache.fineract.infrastructure.reportmailingjob.exception.ReportMailingJobNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ReportMailingJobRepositoryWrapper {
+ private final ReportMailingJobRepository reportMailingJobRepository;
+
+ @Autowired
+ public ReportMailingJobRepositoryWrapper(final ReportMailingJobRepository reportMailingJobRepository) {
+ this.reportMailingJobRepository = reportMailingJobRepository;
+ }
+
+ /**
+ * find report mailing job by ID, throw a "entity not found" exception if the job does not exist
+ *
+ * @param id -- the identifier of the report mailing job to be found
+ * @return ReportMailingJob object
+ **/
+ public ReportMailingJob findOneThrowExceptionIfNotFound(final Long id) {
+ final ReportMailingJob reportMailingJob = this.reportMailingJobRepository.findOne(id);
+
+ if (reportMailingJob == null || reportMailingJob.isDeleted()) {
+ throw new ReportMailingJobNotFoundException(id);
+ }
+
+ return reportMailingJob;
+ }
+
+ /**
+ * @return ReportMailingJobRepository Jpa Repository object
+ **/
+ public ReportMailingJobRepository getReportMailingJobRepository() {
+ return this.reportMailingJobRepository;
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/domain/ReportMailingJobRunHistory.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/domain/ReportMailingJobRunHistory.java
new file mode 100644
index 000000000..4839ed637
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/domain/ReportMailingJobRunHistory.java
@@ -0,0 +1,139 @@
+/**
+ * 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.reportmailingjob.domain;
+
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import org.joda.time.DateTime;
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_report_mailing_job_run_history")
+public class ReportMailingJobRunHistory extends AbstractPersistable {
+ private static final long serialVersionUID = -3757370929988421076L;
+
+ @ManyToOne
+ @JoinColumn(name = "job_id", nullable = false)
+ private ReportMailingJob reportMailingJob;
+
+ @Column(name = "start_datetime", nullable = false)
+ @Temporal(TemporalType.TIMESTAMP)
+ private Date startDateTime;
+
+ @Column(name = "end_datetime", nullable = false)
+ @Temporal(TemporalType.TIMESTAMP)
+ private Date endDateTime;
+
+ @Column(name = "status", nullable = false)
+ private String status;
+
+ @Column(name = "error_message", nullable = false)
+ private String errorMessage;
+
+ @Column(name = "error_log", nullable = false)
+ private String errorLog;
+
+ /**
+ * ReportMailingJobRunHistory protected constructor
+ **/
+ protected ReportMailingJobRunHistory() { }
+
+ /**
+ * ReportMailingJobRunHistory private constructor
+ **/
+ private ReportMailingJobRunHistory(final ReportMailingJob reportMailingJob, final DateTime startDateTime, final DateTime endDateTime, final String status,
+ final String errorMessage, final String errorLog) {
+ this.reportMailingJob = reportMailingJob;
+ this.startDateTime = null;
+
+ if (startDateTime != null) {
+ this.startDateTime = startDateTime.toDate();
+ }
+
+ this.endDateTime = null;
+
+ if (endDateTime != null) {
+ this.endDateTime = endDateTime.toDate();
+ }
+
+ this.status = status;
+ this.errorMessage = errorMessage;
+ this.errorLog = errorLog;
+ }
+
+ /**
+ * Creates an instance of the ReportMailingJobRunHistory class
+ *
+ * @return ReportMailingJobRunHistory object
+ **/
+ public static ReportMailingJobRunHistory newInstance(final ReportMailingJob reportMailingJob, final DateTime startDateTime, final DateTime endDateTime,
+ final String status, final String errorMessage, final String errorLog) {
+ return new ReportMailingJobRunHistory(reportMailingJob, startDateTime, endDateTime, status, errorMessage, errorLog);
+ }
+
+ /**
+ * @return the reportMailingJobId
+ */
+ public ReportMailingJob getReportMailingJob() {
+ return this.reportMailingJob;
+ }
+
+ /**
+ * @return the startDateTime
+ */
+ public DateTime getStartDateTime() {
+ return (this.startDateTime != null) ? new DateTime(this.startDateTime) : null;
+ }
+
+ /**
+ * @return the endDateTime
+ */
+ public DateTime getEndDateTime() {
+ return (this.endDateTime != null) ? new DateTime(this.endDateTime) : null;
+ }
+
+ /**
+ * @return the status
+ */
+ public String getStatus() {
+ return status;
+ }
+
+ /**
+ * @return the errorMessage
+ */
+ public String getErrorMessage() {
+ return errorMessage;
+ }
+
+ /**
+ * @return the errorLog
+ */
+ public String getErrorLog() {
+ return errorLog;
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/domain/ReportMailingJobRunHistoryRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/domain/ReportMailingJobRunHistoryRepository.java
new file mode 100644
index 000000000..be6cdfaa8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/domain/ReportMailingJobRunHistoryRepository.java
@@ -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.
+ */
+package org.apache.fineract.infrastructure.reportmailingjob.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface ReportMailingJobRunHistoryRepository extends JpaRepository, JpaSpecificationExecutor {
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/exception/ReportMailingJobConfigurationNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/exception/ReportMailingJobConfigurationNotFoundException.java
new file mode 100644
index 000000000..9d45449eb
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/exception/ReportMailingJobConfigurationNotFoundException.java
@@ -0,0 +1,33 @@
+/**
+ * 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.reportmailingjob.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when report mailing job configuration resources are not found.
+ **/
+@SuppressWarnings("serial")
+public class ReportMailingJobConfigurationNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+ public ReportMailingJobConfigurationNotFoundException(final String name) {
+ super("error.msg.report.mailing.job.configuration.name.invalid", "Report mailing job configuration with name " + name +
+ " does not exist", name);
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/exception/ReportMailingJobNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/exception/ReportMailingJobNotFoundException.java
new file mode 100644
index 000000000..b8e662681
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/exception/ReportMailingJobNotFoundException.java
@@ -0,0 +1,33 @@
+/**
+ * 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.reportmailingjob.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when report mailing job resources are not found.
+ **/
+@SuppressWarnings("serial")
+public class ReportMailingJobNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+ public ReportMailingJobNotFoundException(final Long reportMailingJobId) {
+ super("error.msg.report.mailing.job.id.invalid", "Report mailing job with identifier " + reportMailingJobId +
+ " does not exist", reportMailingJobId);
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/exception/ReportMailingJobRunHistoryNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/exception/ReportMailingJobRunHistoryNotFoundException.java
new file mode 100644
index 000000000..a69999bb5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/exception/ReportMailingJobRunHistoryNotFoundException.java
@@ -0,0 +1,32 @@
+/**
+ * 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.reportmailingjob.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when report mailing job run history resources are not found.
+ **/
+@SuppressWarnings("serial")
+public class ReportMailingJobRunHistoryNotFoundException extends AbstractPlatformResourceNotFoundException {
+ public ReportMailingJobRunHistoryNotFoundException(final Long reportMailingJobId) {
+ super("error.msg.report.mailing.job.run.history.invalid", "Report mailing job run history with job identifier "
+ + reportMailingJobId + " does not exist", reportMailingJobId);
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/handler/CreateReportMailingJobCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/handler/CreateReportMailingJobCommandHandler.java
new file mode 100644
index 000000000..501caedca
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/handler/CreateReportMailingJobCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * 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.reportmailingjob.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.reportmailingjob.ReportMailingJobConstants;
+import org.apache.fineract.infrastructure.reportmailingjob.service.ReportMailingJobWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = ReportMailingJobConstants.REPORT_MAILING_JOB_ENTITY_NAME, action = "CREATE")
+public class CreateReportMailingJobCommandHandler implements NewCommandSourceHandler {
+ private final ReportMailingJobWritePlatformService reportMailingJobWritePlatformService;
+
+ @Autowired
+ public CreateReportMailingJobCommandHandler(final ReportMailingJobWritePlatformService reportMailingJobWritePlatformService) {
+ this.reportMailingJobWritePlatformService = reportMailingJobWritePlatformService;
+ }
+
+ @Override
+ @Transactional
+ public CommandProcessingResult processCommand(JsonCommand jsonCommand) {
+ return this.reportMailingJobWritePlatformService.createReportMailingJob(jsonCommand);
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/handler/DeleteReportMailingJobCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/handler/DeleteReportMailingJobCommandHandler.java
new file mode 100644
index 000000000..51d57d756
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/handler/DeleteReportMailingJobCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * 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.reportmailingjob.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.reportmailingjob.ReportMailingJobConstants;
+import org.apache.fineract.infrastructure.reportmailingjob.service.ReportMailingJobWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = ReportMailingJobConstants.REPORT_MAILING_JOB_ENTITY_NAME, action = "DELETE")
+public class DeleteReportMailingJobCommandHandler implements NewCommandSourceHandler {
+ private final ReportMailingJobWritePlatformService reportMailingJobWritePlatformService;
+
+ @Autowired
+ public DeleteReportMailingJobCommandHandler(final ReportMailingJobWritePlatformService reportMailingJobWritePlatformService) {
+ this.reportMailingJobWritePlatformService = reportMailingJobWritePlatformService;
+ }
+
+ @Override
+ @Transactional
+ public CommandProcessingResult processCommand(JsonCommand jsonCommand) {
+ return this.reportMailingJobWritePlatformService.deleteReportMailingJob(jsonCommand.entityId());
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/handler/UpdateReportMailingJobCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/handler/UpdateReportMailingJobCommandHandler.java
new file mode 100644
index 000000000..7622ca6a2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/handler/UpdateReportMailingJobCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * 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.reportmailingjob.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.reportmailingjob.ReportMailingJobConstants;
+import org.apache.fineract.infrastructure.reportmailingjob.service.ReportMailingJobWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = ReportMailingJobConstants.REPORT_MAILING_JOB_ENTITY_NAME, action = "UPDATE")
+public class UpdateReportMailingJobCommandHandler implements NewCommandSourceHandler {
+ private final ReportMailingJobWritePlatformService reportMailingJobWritePlatformService;
+
+ @Autowired
+ public UpdateReportMailingJobCommandHandler(final ReportMailingJobWritePlatformService reportMailingJobWritePlatformService) {
+ this.reportMailingJobWritePlatformService = reportMailingJobWritePlatformService;
+ }
+
+ @Override
+ @Transactional
+ public CommandProcessingResult processCommand(JsonCommand jsonCommand) {
+ return this.reportMailingJobWritePlatformService.updateReportMailingJob(jsonCommand.entityId(), jsonCommand);
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobConfigurationReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobConfigurationReadPlatformService.java
new file mode 100644
index 000000000..4812ae070
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobConfigurationReadPlatformService.java
@@ -0,0 +1,39 @@
+/**
+ * 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.reportmailingjob.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.reportmailingjob.data.ReportMailingJobConfigurationData;
+
+public interface ReportMailingJobConfigurationReadPlatformService {
+ /**
+ * Retrieve all report mailing job configurations
+ *
+ * @return ReportMailingJobConfigurationData object
+ **/
+ Collection retrieveAllReportMailingJobConfigurations();
+
+ /**
+ * Retrieve report mailing job configuration by name
+ *
+ * @return ReportMailingJobConfigurationData object
+ **/
+ ReportMailingJobConfigurationData retrieveReportMailingJobConfiguration(final String name);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobConfigurationReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobConfigurationReadPlatformServiceImpl.java
new file mode 100644
index 000000000..fd8dfac66
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobConfigurationReadPlatformServiceImpl.java
@@ -0,0 +1,84 @@
+/**
+ * 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.reportmailingjob.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.reportmailingjob.data.ReportMailingJobConfigurationData;
+import org.apache.fineract.infrastructure.reportmailingjob.exception.ReportMailingJobConfigurationNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ReportMailingJobConfigurationReadPlatformServiceImpl implements ReportMailingJobConfigurationReadPlatformService {
+ private final JdbcTemplate jdbcTemplate;
+
+ /**
+ * ReportMailingJobConfigurationReadPlatformServiceImpl constructor
+ **/
+ @Autowired
+ public ReportMailingJobConfigurationReadPlatformServiceImpl(final RoutingDataSource dataSource) {
+ this.jdbcTemplate = new JdbcTemplate(dataSource);
+ }
+
+ @Override
+ public Collection retrieveAllReportMailingJobConfigurations() {
+ final ReportMailingJobConfigurationMapper mapper = new ReportMailingJobConfigurationMapper();
+ final String sql = "select " + mapper.ReportMailingJobConfigurationSchema();
+
+ return this.jdbcTemplate.query(sql, mapper, new Object[] {});
+ }
+
+ @Override
+ public ReportMailingJobConfigurationData retrieveReportMailingJobConfiguration(String name) {
+ try {
+ final ReportMailingJobConfigurationMapper mapper = new ReportMailingJobConfigurationMapper();
+ final String sql = "select " + mapper.ReportMailingJobConfigurationSchema() + " where rmjc.name = ?";
+
+ return this.jdbcTemplate.queryForObject(sql, mapper, new Object[] { name });
+ }
+
+ catch (final EmptyResultDataAccessException ex) {
+ throw new ReportMailingJobConfigurationNotFoundException(name);
+ }
+ }
+
+ private static final class ReportMailingJobConfigurationMapper implements RowMapper {
+ public String ReportMailingJobConfigurationSchema() {
+ return "rmjc.id, rmjc.name, rmjc.value "
+ + "from m_report_mailing_job_configuration rmjc";
+ }
+
+ @Override
+ public ReportMailingJobConfigurationData mapRow(ResultSet rs, int rowNum) throws SQLException {
+ final Integer id = JdbcSupport.getInteger(rs, "id");
+ final String name = rs.getString("name");
+ final String value = rs.getString("value");
+
+ return ReportMailingJobConfigurationData.newInstance(id, name, value);
+ }
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobEmailService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobEmailService.java
new file mode 100644
index 000000000..5df05a31b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobEmailService.java
@@ -0,0 +1,30 @@
+/**
+ * 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.reportmailingjob.service;
+
+import org.apache.fineract.infrastructure.reportmailingjob.data.ReportMailingJobEmailData;
+
+public interface ReportMailingJobEmailService {
+ /**
+ * Handles request to send an email with attachment
+ *
+ * @param reportMailingJobEmailData {@link ReportMailingJobEmailData} object
+ */
+ void sendEmailWithAttachment(final ReportMailingJobEmailData reportMailingJobEmailData);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobEmailServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobEmailServiceImpl.java
new file mode 100644
index 000000000..58bb09a6d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobEmailServiceImpl.java
@@ -0,0 +1,162 @@
+/**
+ * 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.reportmailingjob.service;
+
+import java.util.Collection;
+import java.util.Properties;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.reportmailingjob.ReportMailingJobConstants;
+import org.apache.fineract.infrastructure.reportmailingjob.data.ReportMailingJobConfigurationData;
+import org.apache.fineract.infrastructure.reportmailingjob.data.ReportMailingJobEmailData;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.mail.javamail.JavaMailSenderImpl;
+import org.springframework.mail.javamail.MimeMessageHelper;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ReportMailingJobEmailServiceImpl implements ReportMailingJobEmailService {
+ private final ReportMailingJobConfigurationReadPlatformService reportMailingJobConfigurationReadPlatformService;
+ private Collection reportMailingJobConfigurationDataCollection;
+
+ /**
+ * ReportMailingJobEmailServiceImpl constructor
+ **/
+ @Autowired
+ public ReportMailingJobEmailServiceImpl(final ReportMailingJobConfigurationReadPlatformService reportMailingJobConfigurationReadPlatformService) {
+ this.reportMailingJobConfigurationReadPlatformService = reportMailingJobConfigurationReadPlatformService;
+
+ }
+
+ @Override
+ public void sendEmailWithAttachment(ReportMailingJobEmailData reportMailingJobEmailData) {
+ try {
+ // get all ReportMailingJobConfiguration objects from the database
+ this.reportMailingJobConfigurationDataCollection = this.reportMailingJobConfigurationReadPlatformService.
+ retrieveAllReportMailingJobConfigurations();
+
+ JavaMailSenderImpl javaMailSenderImpl = new JavaMailSenderImpl();
+ javaMailSenderImpl.setHost(this.getGmailSmtpServer());
+ javaMailSenderImpl.setPort(this.getGmailSmtpPort());
+ javaMailSenderImpl.setUsername(this.getGmailSmtpUsername());
+ javaMailSenderImpl.setPassword(this.getGmailSmtpPassword());
+ javaMailSenderImpl.setJavaMailProperties(this.getJavaMailProperties());
+
+ MimeMessage mimeMessage = javaMailSenderImpl.createMimeMessage();
+
+ // use the true flag to indicate you need a multipart message
+ MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);
+
+ mimeMessageHelper.setTo(reportMailingJobEmailData.getTo());
+ mimeMessageHelper.setText(reportMailingJobEmailData.getText());
+ mimeMessageHelper.setSubject(reportMailingJobEmailData.getSubject());
+
+ if (reportMailingJobEmailData.getAttachment() != null) {
+ mimeMessageHelper.addAttachment(reportMailingJobEmailData.getAttachment().getName(), reportMailingJobEmailData.getAttachment());
+ }
+
+ javaMailSenderImpl.send(mimeMessage);
+ }
+
+ catch (MessagingException e) {
+ // handle the exception
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * @return Properties object containing JavaMail properties
+ **/
+ private Properties getJavaMailProperties() {
+ Properties properties = new Properties();
+
+ properties.setProperty("mail.smtp.auth", "true");
+ properties.setProperty("mail.smtp.starttls.enable", "true");
+ properties.setProperty("mail.smtp.ssl.trust", this.getGmailSmtpServer());
+
+ return properties;
+ }
+
+ /**
+ * get a report mailing job configuration object by name from collection of objects
+ *
+ * @param name -- the value of the name property
+ * @return ReportMailingJobConfigurationData object
+ **/
+ private ReportMailingJobConfigurationData getReportMailingJobConfigurationData(final String name) {
+ ReportMailingJobConfigurationData reportMailingJobConfigurationData = null;
+
+ if (this.reportMailingJobConfigurationDataCollection != null && !this.reportMailingJobConfigurationDataCollection.isEmpty()) {
+ for (ReportMailingJobConfigurationData reportMailingJobConfigurationDataObject : this.reportMailingJobConfigurationDataCollection) {
+ String configurationName = reportMailingJobConfigurationDataObject.getName();
+
+ if (!StringUtils.isEmpty(configurationName) && configurationName.equals(name)) {
+ reportMailingJobConfigurationData = reportMailingJobConfigurationDataObject;
+ break;
+ }
+ }
+ }
+
+ return reportMailingJobConfigurationData;
+ }
+
+ /**
+ * @return Gmail smtp server name
+ **/
+ private String getGmailSmtpServer() {
+ final ReportMailingJobConfigurationData reportMailingJobConfigurationData = this.getReportMailingJobConfigurationData
+ (ReportMailingJobConstants.GMAIL_SMTP_SERVER);
+
+ return (reportMailingJobConfigurationData != null) ? reportMailingJobConfigurationData.getValue() : null;
+ }
+
+ /**
+ * @return Gmail smtp server port number
+ **/
+ private Integer getGmailSmtpPort() {
+ final ReportMailingJobConfigurationData reportMailingJobConfigurationData = this.getReportMailingJobConfigurationData
+ (ReportMailingJobConstants.GMAIL_SMTP_PORT);
+ final String portNumber = (reportMailingJobConfigurationData != null) ? reportMailingJobConfigurationData.getValue() : null;
+
+ return (portNumber != null) ? Integer.parseInt(portNumber) : null;
+ }
+
+ /**
+ * @return Gmail smtp username
+ **/
+ private String getGmailSmtpUsername() {
+ final ReportMailingJobConfigurationData reportMailingJobConfigurationData = this.getReportMailingJobConfigurationData
+ (ReportMailingJobConstants.GMAIL_SMTP_USERNAME);
+
+ return (reportMailingJobConfigurationData != null) ? reportMailingJobConfigurationData.getValue() : null;
+ }
+
+ /**
+ * @return Gmail smtp password
+ **/
+ private String getGmailSmtpPassword() {
+ final ReportMailingJobConfigurationData reportMailingJobConfigurationData = this.getReportMailingJobConfigurationData
+ (ReportMailingJobConstants.GMAIL_SMTP_PASSWORD);
+
+ return (reportMailingJobConfigurationData != null) ? reportMailingJobConfigurationData.getValue() : null;
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobReadPlatformService.java
new file mode 100644
index 000000000..3ac274d4a
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobReadPlatformService.java
@@ -0,0 +1,47 @@
+/**
+ * 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.reportmailingjob.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.infrastructure.reportmailingjob.data.ReportMailingJobData;
+
+public interface ReportMailingJobReadPlatformService {
+ /**
+ * Retrieve all report mailing jobs that have the isDeleted property set to 0
+ **/
+ Page retrieveAllReportMailingJobs(SearchParameters searchParameters);
+
+ /**
+ * Retrieve a report mailing job that has the isDeleted property set to 0
+ **/
+ ReportMailingJobData retrieveReportMailingJob(Long reportMailingJobId);
+
+ /**
+ * Retrieve the report mailing job enumeration/dropdown options
+ **/
+ ReportMailingJobData retrieveReportMailingJobEnumOptions();
+
+ /**
+ * Retrieve all active report mailing jobs that have their isDeleted property set to 0
+ **/
+ Collection retrieveAllActiveReportMailingJobs();
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobReadPlatformServiceImpl.java
new file mode 100644
index 000000000..afec18008
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobReadPlatformServiceImpl.java
@@ -0,0 +1,203 @@
+/**
+ * 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.reportmailingjob.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.PaginationHelper;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.infrastructure.dataqueries.data.ReportData;
+import org.apache.fineract.infrastructure.reportmailingjob.data.ReportMailingJobData;
+import org.apache.fineract.infrastructure.reportmailingjob.data.ReportMailingJobEmailAttachmentFileFormat;
+import org.apache.fineract.infrastructure.reportmailingjob.data.ReportMailingJobStretchyReportParamDateOption;
+import org.apache.fineract.infrastructure.reportmailingjob.data.ReportMailingJobTimelineData;
+import org.apache.fineract.infrastructure.reportmailingjob.exception.ReportMailingJobNotFoundException;
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ReportMailingJobReadPlatformServiceImpl implements ReportMailingJobReadPlatformService {
+ private final JdbcTemplate jdbcTemplate;
+
+ @Autowired
+ public ReportMailingJobReadPlatformServiceImpl(final RoutingDataSource dataSource) {
+ this.jdbcTemplate = new JdbcTemplate(dataSource);
+ }
+
+ @Override
+ public Page retrieveAllReportMailingJobs(final SearchParameters searchParameters) {
+ final StringBuilder sqlStringBuilder = new StringBuilder(200);
+ final List queryParameters = new ArrayList<>();
+ final ReportMailingJobMapper mapper = new ReportMailingJobMapper();
+ final PaginationHelper paginationHelper = new PaginationHelper<>();
+
+ sqlStringBuilder.append("select SQL_CALC_FOUND_ROWS ");
+ sqlStringBuilder.append(mapper.ReportMailingJobSchema());
+ sqlStringBuilder.append(" where rmj.is_deleted = 0");
+
+ if (searchParameters.isOrderByRequested()) {
+ sqlStringBuilder.append(" order by ").append(searchParameters.getOrderBy());
+
+ if (searchParameters.isSortOrderProvided()) {
+ sqlStringBuilder.append(" ").append(searchParameters.getSortOrder());
+ }
+ } else {
+ sqlStringBuilder.append(" order by rmj.name ");
+ }
+
+ if (searchParameters.isLimited()) {
+ sqlStringBuilder.append(" limit ").append(searchParameters.getLimit());
+
+ if (searchParameters.isOffset()) {
+ sqlStringBuilder.append(" offset ").append(searchParameters.getOffset());
+ }
+ }
+
+ return paginationHelper.fetchPage(this.jdbcTemplate, "SELECT FOUND_ROWS()", sqlStringBuilder.toString(),
+ queryParameters.toArray(), mapper);
+ }
+
+ @Override
+ public Collection retrieveAllActiveReportMailingJobs() {
+ final ReportMailingJobMapper mapper = new ReportMailingJobMapper();
+ final String sql = "select " + mapper.ReportMailingJobSchema() + " where rmj.is_deleted = 0 and is_active = 1"
+ + " order by rmj.name";
+
+ return this.jdbcTemplate.query(sql, mapper, new Object[] {});
+ }
+
+ @Override
+ public ReportMailingJobData retrieveReportMailingJob(final Long reportMailingJobId) {
+ try {
+ final ReportMailingJobMapper mapper = new ReportMailingJobMapper();
+ final String sql = "select " + mapper.ReportMailingJobSchema() + " where rmj.id = ? and rmj.is_deleted = 0";
+
+ return this.jdbcTemplate.queryForObject(sql, mapper, new Object[] { reportMailingJobId });
+ }
+
+ catch (final EmptyResultDataAccessException ex) {
+ throw new ReportMailingJobNotFoundException(reportMailingJobId);
+ }
+ }
+
+ @Override
+ public ReportMailingJobData retrieveReportMailingJobEnumOptions() {
+ final List emailAttachmentFileFormatOptions = ReportMailingJobEmailAttachmentFileFormat.validOptions();
+ final List stretchyReportParamDateOptions = ReportMailingJobStretchyReportParamDateOption.validOptions();
+
+ return ReportMailingJobData.newInstance(emailAttachmentFileFormatOptions, stretchyReportParamDateOptions);
+ }
+
+ private static final class ReportMailingJobMapper implements RowMapper {
+ public String ReportMailingJobSchema() {
+ return "rmj.id, rmj.name, rmj.description, rmj.start_datetime as startDateTime, rmj.recurrence, rmj.created_date as createdOnDate, "
+ + "cbu.username as createdByUsername, cbu.firstname as createdByFirstname, cbu.lastname as createdByLastname, "
+ + "rmj.lastmodified_date as updatedOnDate, "
+ + "mbu.username as updatedByUsername, mbu.firstname as updatedByFirstname, mbu.lastname as updatedByLastname, "
+ + "rmj.email_recipients as emailRecipients, "
+ + "rmj.email_subject as emailSubject, rmj.email_message as emailMessage, "
+ + "rmj.email_attachment_file_format as emailAttachmentFileFormat, "
+ + "rmj.stretchy_report_param_map as stretchyReportParamMap, rmj.previous_run_datetime as previousRunDateTime, "
+ + "rmj.next_run_datetime as nextRunDateTime, rmj.previous_run_status as previousRunStatus, "
+ + "rmj.previous_run_error_log as previousRunErrorLog, rmj.previous_run_error_message as previousRunErrorMessage, "
+ + "rmj.number_of_runs as numberOfRuns, rmj.is_active as isActive, rmj.run_as_userid as runAsUserId, "
+ + "sr.id as reportId, sr.report_name as reportName, sr.report_type as reportType, sr.report_subtype as reportSubType, "
+ + "sr.report_category as reportCategory, sr.report_sql as reportSql, sr.description as reportDescription, "
+ + "sr.core_report as coreReport, sr.use_report as useReport "
+ + "from m_report_mailing_job rmj "
+ + "inner join m_appuser cbu "
+ + "on cbu.id = rmj.createdby_id "
+ + "left join m_appuser mbu "
+ + "on mbu.id = rmj.lastmodifiedby_id "
+ + "left join stretchy_report sr "
+ + "on rmj.stretchy_report_id = sr.id";
+ }
+
+ @Override
+ public ReportMailingJobData mapRow(ResultSet rs, int rowNum) throws SQLException {
+ final Long id = rs.getLong("id");
+ final String name = rs.getString("name");
+ final String description = rs.getString("description");
+ final DateTime startDateTime = JdbcSupport.getDateTime(rs, "startDateTime");
+ final String recurrence = rs.getString("recurrence");
+ final LocalDate createdOnDate = JdbcSupport.getLocalDate(rs, "createdOnDate");
+ final LocalDate updatedOnDate = JdbcSupport.getLocalDate(rs, "updatedOnDate");
+ final String emailRecipients = rs.getString("emailRecipients");
+ final String emailSubject = rs.getString("emailSubject");
+ final String emailMessage = rs.getString("emailMessage");
+ final String emailAttachmentFileFormatString = rs.getString("emailAttachmentFileFormat");
+ EnumOptionData emailAttachmentFileFormat = null;
+
+ if (emailAttachmentFileFormatString != null) {
+ ReportMailingJobEmailAttachmentFileFormat format = ReportMailingJobEmailAttachmentFileFormat.newInstance(emailAttachmentFileFormatString);
+
+ emailAttachmentFileFormat = format.toEnumOptionData();
+ }
+
+ final String stretchyReportParamMap = rs.getString("stretchyReportParamMap");
+ final DateTime previousRunDateTime = JdbcSupport.getDateTime(rs, "previousRunDateTime");
+ final DateTime nextRunDateTime = JdbcSupport.getDateTime(rs, "nextRunDateTime");
+ final String previousRunStatus = rs.getString("previousRunStatus");
+ final String previousRunErrorLog = rs.getString("previousRunErrorLog");
+ final String previousRunErrorMessage = rs.getString("previousRunErrorMessage");
+ final Integer numberOfRuns = JdbcSupport.getInteger(rs, "numberOfRuns");
+ final boolean isActive = rs.getBoolean("isActive");
+ final String createdByUsername = rs.getString("createdByUsername");
+ final String createdByFirstname = rs.getString("createdByFirstname");
+ final String createdByLastname = rs.getString("createdByLastname");
+ final String updatedByUsername = rs.getString("updatedByUsername");
+ final String updatedByFirstname = rs.getString("updatedByFirstname");
+ final String updatedByLastname = rs.getString("updatedByLastname");
+ final ReportMailingJobTimelineData timeline = new ReportMailingJobTimelineData(createdOnDate, createdByUsername,
+ createdByFirstname, createdByLastname, updatedOnDate, updatedByUsername, updatedByFirstname, updatedByLastname);
+ final Long runAsUserId = JdbcSupport.getLong(rs, "runAsUserId");
+
+ final Long reportId = JdbcSupport.getLong(rs, "reportId");
+ final String reportName = rs.getString("reportName");
+ final String reportType = rs.getString("reportType");
+ final String reportSubType = rs.getString("reportSubType");
+ final String reportCategory = rs.getString("reportCategory");
+ final String reportSql = rs.getString("reportSql");
+ final String reportDescription = rs.getString("reportDescription");
+ final boolean coreReport = rs.getBoolean("coreReport");
+ final boolean useReport = rs.getBoolean("useReport");
+
+ final ReportData stretchyReport = new ReportData(reportId, reportName, reportType, reportSubType, reportCategory,
+ reportDescription, reportSql, coreReport, useReport, null);
+
+ return ReportMailingJobData.newInstance(id, name, description, startDateTime, recurrence, timeline, emailRecipients,
+ emailSubject, emailMessage, emailAttachmentFileFormat, stretchyReport, stretchyReportParamMap, previousRunDateTime,
+ nextRunDateTime, previousRunStatus, previousRunErrorLog, previousRunErrorMessage, numberOfRuns, isActive,
+ runAsUserId);
+ }
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobRunHistoryReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobRunHistoryReadPlatformService.java
new file mode 100644
index 000000000..9f4a20412
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobRunHistoryReadPlatformService.java
@@ -0,0 +1,34 @@
+/**
+ * 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.reportmailingjob.service;
+
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.infrastructure.reportmailingjob.data.ReportMailingJobRunHistoryData;
+
+public interface ReportMailingJobRunHistoryReadPlatformService {
+ /**
+ * Retrieve all report mailing run history with similar job id to the one passed
+ *
+ * @param reportMailingJobId ReportMailingJob identifier
+ * @param searchParameters {@link SearchParameters} object
+ * @return collection of {@link ReportMailingJobRunHistoryData} objects
+ **/
+ Page retrieveRunHistoryByJobId(Long reportMailingJobId, SearchParameters searchParameters);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobRunHistoryReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobRunHistoryReadPlatformServiceImpl.java
new file mode 100644
index 000000000..4aeb68f1f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobRunHistoryReadPlatformServiceImpl.java
@@ -0,0 +1,106 @@
+/**
+ * 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.reportmailingjob.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.PaginationHelper;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
+import org.apache.fineract.infrastructure.reportmailingjob.data.ReportMailingJobRunHistoryData;
+import org.joda.time.DateTime;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ReportMailingJobRunHistoryReadPlatformServiceImpl implements ReportMailingJobRunHistoryReadPlatformService {
+ private final JdbcTemplate jdbcTemplate;
+ private final ReportMailingJobRunHistoryMapper reportMailingJobRunHistoryMapper;
+ private final PaginationHelper paginationHelper = new PaginationHelper<>();
+
+ @Autowired
+ public ReportMailingJobRunHistoryReadPlatformServiceImpl(final RoutingDataSource dataSource) {
+ this.jdbcTemplate = new JdbcTemplate(dataSource);
+ this.reportMailingJobRunHistoryMapper = new ReportMailingJobRunHistoryMapper();
+ }
+
+ @Override
+ public Page retrieveRunHistoryByJobId(final Long reportMailingJobId,
+ final SearchParameters searchParameters) {
+ final StringBuilder sqlStringBuilder = new StringBuilder(200);
+ final List queryParameters = new ArrayList<>();
+
+ sqlStringBuilder.append("select SQL_CALC_FOUND_ROWS ");
+ sqlStringBuilder.append(this.reportMailingJobRunHistoryMapper.ReportMailingJobRunHistorySchema());
+
+ if (reportMailingJobId != null) {
+ sqlStringBuilder.append(" where rmjrh.job_id = ? ");
+ queryParameters.add(reportMailingJobId);
+ }
+
+ if (searchParameters.isOrderByRequested()) {
+ sqlStringBuilder.append(" order by ").append(searchParameters.getOrderBy());
+
+ if (searchParameters.isSortOrderProvided()) {
+ sqlStringBuilder.append(" ").append(searchParameters.getSortOrder());
+ }
+ }
+
+ if (searchParameters.isLimited()) {
+ sqlStringBuilder.append(" limit ").append(searchParameters.getLimit());
+
+ if (searchParameters.isOffset()) {
+ sqlStringBuilder.append(" offset ").append(searchParameters.getOffset());
+ }
+ }
+
+ return this.paginationHelper.fetchPage(this.jdbcTemplate, "SELECT FOUND_ROWS()", sqlStringBuilder.toString(),
+ queryParameters.toArray(), this.reportMailingJobRunHistoryMapper);
+ }
+
+ private static final class ReportMailingJobRunHistoryMapper implements RowMapper {
+ public String ReportMailingJobRunHistorySchema() {
+ return "rmjrh.id, rmjrh.job_id as reportMailingJobId, rmjrh.start_datetime as startDateTime, "
+ + "rmjrh.end_datetime as endDateTime, rmjrh.status, rmjrh.error_message as errorMessage, "
+ + "rmjrh.error_log as errorLog "
+ + "from m_report_mailing_job_run_history rmjrh";
+ }
+
+ @Override
+ public ReportMailingJobRunHistoryData mapRow(ResultSet rs, int rowNum) throws SQLException {
+ final Long id = JdbcSupport.getLong(rs, "id");
+ final Long reportMailingJobId = JdbcSupport.getLong(rs, "reportMailingJobId");
+ final DateTime startDateTime = JdbcSupport.getDateTime(rs, "startDateTime");
+ final DateTime endDateTime = JdbcSupport.getDateTime(rs, "endDateTime");
+ final String status = rs.getString("status");
+ final String errorMessage = rs.getString("errorMessage");
+ final String errorLog = rs.getString("errorLog");
+
+ return ReportMailingJobRunHistoryData.newInstance(id, reportMailingJobId, startDateTime, endDateTime, status,
+ errorMessage, errorLog);
+ }
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobWritePlatformService.java
new file mode 100644
index 000000000..ce549191c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobWritePlatformService.java
@@ -0,0 +1,30 @@
+/**
+ * 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.reportmailingjob.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
+
+public interface ReportMailingJobWritePlatformService {
+ CommandProcessingResult createReportMailingJob(JsonCommand jsonCommand);
+ CommandProcessingResult updateReportMailingJob(Long reportMailingJobId, JsonCommand jsonCommand);
+ CommandProcessingResult deleteReportMailingJob(Long reportMailingJobId);
+ void executeReportMailingJobs() throws JobExecutionException;
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobWritePlatformServiceImpl.java
new file mode 100644
index 000000000..02972a927
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobWritePlatformServiceImpl.java
@@ -0,0 +1,495 @@
+/**
+ * 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.reportmailingjob.service;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.dataqueries.domain.Report;
+import org.apache.fineract.infrastructure.dataqueries.domain.ReportRepositoryWrapper;
+import org.apache.fineract.infrastructure.dataqueries.service.ReadReportingService;
+import org.apache.fineract.infrastructure.documentmanagement.contentrepository.FileSystemContentRepository;
+import org.apache.fineract.infrastructure.jobs.annotation.CronTarget;
+import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
+import org.apache.fineract.infrastructure.jobs.service.JobName;
+import org.apache.fineract.infrastructure.report.provider.ReportingProcessServiceProvider;
+import org.apache.fineract.infrastructure.report.service.ReportingProcessService;
+import org.apache.fineract.infrastructure.reportmailingjob.ReportMailingJobConstants;
+import org.apache.fineract.infrastructure.reportmailingjob.data.ReportMailingJobEmailAttachmentFileFormat;
+import org.apache.fineract.infrastructure.reportmailingjob.data.ReportMailingJobEmailData;
+import org.apache.fineract.infrastructure.reportmailingjob.data.ReportMailingJobPreviousRunStatus;
+import org.apache.fineract.infrastructure.reportmailingjob.data.ReportMailingJobStretchyReportParamDateOption;
+import org.apache.fineract.infrastructure.reportmailingjob.domain.ReportMailingJob;
+import org.apache.fineract.infrastructure.reportmailingjob.domain.ReportMailingJobRepository;
+import org.apache.fineract.infrastructure.reportmailingjob.domain.ReportMailingJobRepositoryWrapper;
+import org.apache.fineract.infrastructure.reportmailingjob.domain.ReportMailingJobRunHistory;
+import org.apache.fineract.infrastructure.reportmailingjob.domain.ReportMailingJobRunHistoryRepository;
+import org.apache.fineract.infrastructure.reportmailingjob.util.ReportMailingJobDateUtil;
+import org.apache.fineract.infrastructure.reportmailingjob.validation.ReportMailingJobValidator;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.sun.jersey.core.util.MultivaluedMapImpl;
+
+@Service
+public class ReportMailingJobWritePlatformServiceImpl implements ReportMailingJobWritePlatformService {
+
+ private final static Logger logger = LoggerFactory.getLogger(ReportMailingJobWritePlatformServiceImpl.class);
+ private final ReportRepositoryWrapper reportRepositoryWrapper;
+ private final ReportMailingJobValidator reportMailingJobValidator;
+ private final ReportMailingJobRepositoryWrapper reportMailingJobRepositoryWrapper;
+ private final ReportMailingJobRepository reportMailingJobRepository;
+ private final PlatformSecurityContext platformSecurityContext;
+ private final ReportMailingJobEmailService reportMailingJobEmailService;
+ private final ReadReportingService readReportingService;
+ private final ReportingProcessServiceProvider reportingProcessServiceProvider;
+ private final ReportMailingJobRunHistoryRepository reportMailingJobRunHistoryRepository;
+ private final static String DATETIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
+
+ @Autowired
+ public ReportMailingJobWritePlatformServiceImpl(final ReportRepositoryWrapper reportRepositoryWrapper,
+ final ReportMailingJobValidator reportMailingJobValidator,
+ final ReportMailingJobRepositoryWrapper reportMailingJobRepositoryWrapper,
+ final ReportMailingJobRepository reportMailingJobRepository,
+ final PlatformSecurityContext platformSecurityContext,
+ final ReportMailingJobEmailService reportMailingJobEmailService,
+ final ReadReportingService readReportingService,
+ final ReportMailingJobRunHistoryRepository reportMailingJobRunHistoryRepository,
+ final ReportingProcessServiceProvider reportingProcessServiceProvider) {
+ this.reportRepositoryWrapper = reportRepositoryWrapper;
+ this.reportMailingJobValidator = reportMailingJobValidator;
+ this.reportMailingJobRepositoryWrapper = reportMailingJobRepositoryWrapper;
+ this.reportMailingJobRepository = reportMailingJobRepositoryWrapper.getReportMailingJobRepository();
+ this.platformSecurityContext = platformSecurityContext;
+ this.reportMailingJobEmailService = reportMailingJobEmailService;
+ this.readReportingService = readReportingService;
+ this.reportMailingJobRunHistoryRepository = reportMailingJobRunHistoryRepository;
+ this.reportingProcessServiceProvider = reportingProcessServiceProvider;
+ }
+
+ @Override
+ @Transactional
+ public CommandProcessingResult createReportMailingJob(JsonCommand jsonCommand) {
+ try {
+ // validate the create request
+ this.reportMailingJobValidator.validateCreateRequest(jsonCommand);
+
+ final AppUser appUser = this.platformSecurityContext.authenticatedUser();
+
+ // get the stretchy Report object
+ final Report stretchyReport = this.reportRepositoryWrapper.findOneThrowExceptionIfNotFound(jsonCommand.longValueOfParameterNamed(
+ ReportMailingJobConstants.STRETCHY_REPORT_ID_PARAM_NAME));
+
+ // create an instance of ReportMailingJob class from the JsonCommand object
+ final ReportMailingJob reportMailingJob = ReportMailingJob.newInstance(jsonCommand, stretchyReport, appUser);
+
+ // save entity
+ this.reportMailingJobRepository.save(reportMailingJob);
+
+ return new CommandProcessingResultBuilder().withCommandId(jsonCommand.commandId()).
+ withEntityId(reportMailingJob.getId()).build();
+ } catch (final DataIntegrityViolationException dve) {
+ handleDataIntegrityIssues(jsonCommand, dve);
+
+ return CommandProcessingResult.empty();
+ }
+ }
+
+ @Override
+ @Transactional
+ public CommandProcessingResult updateReportMailingJob(Long reportMailingJobId, JsonCommand jsonCommand) {
+ try {
+ // validate the update request
+ this.reportMailingJobValidator.validateUpdateRequest(jsonCommand);
+
+ // retrieve the ReportMailingJob object from the database
+ final ReportMailingJob reportMailingJob = this.reportMailingJobRepositoryWrapper.findOneThrowExceptionIfNotFound(reportMailingJobId);
+
+ final Map changes = reportMailingJob.update(jsonCommand);
+
+ // get the recurrence rule string
+ final String recurrence = reportMailingJob.getRecurrence();
+
+ // get the next run DateTime from the ReportMailingJob entity
+ DateTime nextRunDateTime = reportMailingJob.getNextRunDateTime();
+
+ // check if the stretchy report id was updated
+ if (changes.containsKey(ReportMailingJobConstants.STRETCHY_REPORT_ID_PARAM_NAME)) {
+ final Long stretchyReportId = (Long) changes.get(ReportMailingJobConstants.STRETCHY_REPORT_ID_PARAM_NAME);
+ final Report stretchyReport = this.reportRepositoryWrapper.findOneThrowExceptionIfNotFound(stretchyReportId);
+
+ // update the stretchy report
+ reportMailingJob.update(stretchyReport);
+ }
+
+ // check if the recurrence was updated
+ if (changes.containsKey(ReportMailingJobConstants.RECURRENCE_PARAM_NAME)) {
+
+ // go ahead if the recurrence is not null
+ if (StringUtils.isNotBlank(recurrence)) {
+ // set the start DateTime to the current tenant date time
+ DateTime startDateTime = DateUtils.getLocalDateTimeOfTenant().toDateTime();
+
+ // check if the start DateTime was updated
+ if (changes.containsKey(ReportMailingJobConstants.START_DATE_TIME_PARAM_NAME)) {
+ // get the updated start DateTime
+ startDateTime = reportMailingJob.getStartDateTime();
+ }
+
+ startDateTime = reportMailingJob.getStartDateTime();
+
+ // get the next recurring DateTime
+ final DateTime nextRecurringDateTime = this.createNextRecurringDateTime(recurrence, startDateTime);
+
+ // update the next run time property
+ reportMailingJob.updateNextRunDateTime(nextRecurringDateTime);
+
+ // check if the next run DateTime is not empty and the recurrence is empty
+ } else if (StringUtils.isBlank(recurrence) && (nextRunDateTime != null)) {
+ // the next run DateTime should be set to null
+ reportMailingJob.updateNextRunDateTime(null);
+ }
+ }
+
+ if (changes.containsKey(ReportMailingJobConstants.START_DATE_TIME_PARAM_NAME)) {
+ final DateTime startDateTime = reportMailingJob.getStartDateTime();
+
+ // initially set the next recurring date time to the new start date time
+ DateTime nextRecurringDateTime = startDateTime;
+
+ // ensure that the recurrence pattern string is not empty
+ if (StringUtils.isNotBlank(recurrence)) {
+ // get the next recurring DateTime
+ nextRecurringDateTime = this.createNextRecurringDateTime(recurrence, startDateTime);
+ }
+
+ // update the next run time property
+ reportMailingJob.updateNextRunDateTime(nextRecurringDateTime);
+ }
+
+ if (!changes.isEmpty()) {
+ // save and flush immediately so any data integrity exception can be handled in the "catch" block
+ this.reportMailingJobRepository.saveAndFlush(reportMailingJob);
+ }
+
+ return new CommandProcessingResultBuilder().
+ withCommandId(jsonCommand.commandId()).
+ withEntityId(reportMailingJob.getId()).
+ with(changes).
+ build();
+ } catch (final DataIntegrityViolationException dve) {
+ handleDataIntegrityIssues(jsonCommand, dve);
+
+ return CommandProcessingResult.empty();
+ }
+ }
+
+ @Override
+ @Transactional
+ public CommandProcessingResult deleteReportMailingJob(Long reportMailingJobId) {
+ // retrieve the ReportMailingJob object from the database
+ final ReportMailingJob reportMailingJob = this.reportMailingJobRepositoryWrapper.findOneThrowExceptionIfNotFound(reportMailingJobId);
+
+ // delete the report mailing job by setting the isDeleted property to 1 and altering the name
+ reportMailingJob.delete();
+
+ // save the report mailing job entity
+ this.reportMailingJobRepository.save(reportMailingJob);
+
+ return new CommandProcessingResultBuilder().withEntityId(reportMailingJobId).build();
+ }
+
+ @Override
+ @CronTarget(jobName = JobName.EXECUTE_REPORT_MAILING_JOBS)
+ public void executeReportMailingJobs() throws JobExecutionException {
+ final Collection reportMailingJobCollection = this.reportMailingJobRepository.findByIsActiveTrueAndIsDeletedFalse();
+
+ for (ReportMailingJob reportMailingJob : reportMailingJobCollection) {
+ // get the tenant's date as a DateTime object
+ final DateTime localDateTimeOftenant = DateUtils.getLocalDateTimeOfTenant().toDateTime();
+ final DateTime nextRunDateTime = reportMailingJob.getNextRunDateTime();
+
+ if (nextRunDateTime != null && nextRunDateTime.isBefore(localDateTimeOftenant)) {
+ // get the emailAttachmentFileFormat enum object
+ final ReportMailingJobEmailAttachmentFileFormat emailAttachmentFileFormat = ReportMailingJobEmailAttachmentFileFormat.
+ newInstance(reportMailingJob.getEmailAttachmentFileFormat());
+
+ if (emailAttachmentFileFormat != null && emailAttachmentFileFormat.isValid()) {
+ final Report stretchyReport = reportMailingJob.getStretchyReport();
+ final String reportName = (stretchyReport != null) ? stretchyReport.getReportName() : null;
+ final StringBuilder errorLog = new StringBuilder();
+ final Map validateStretchyReportParamMap = this.reportMailingJobValidator.
+ validateStretchyReportParamMap(reportMailingJob.getStretchyReportParamMap());
+ MultivaluedMap reportParams = new MultivaluedMapImpl();
+
+ if (validateStretchyReportParamMap != null) {
+ Iterator> validateStretchyReportParamMapEntries = validateStretchyReportParamMap.entrySet().iterator();
+
+ while (validateStretchyReportParamMapEntries.hasNext()) {
+ Map.Entry validateStretchyReportParamMapEntry = validateStretchyReportParamMapEntries.next();
+ String key = validateStretchyReportParamMapEntry.getKey();
+ String value = validateStretchyReportParamMapEntry.getValue();
+
+ if (StringUtils.containsIgnoreCase(key, "date")) {
+ ReportMailingJobStretchyReportParamDateOption reportMailingJobStretchyReportParamDateOption =
+ ReportMailingJobStretchyReportParamDateOption.newInstance(value);
+
+ if (reportMailingJobStretchyReportParamDateOption.isValid()) {
+ value = ReportMailingJobDateUtil.getDateAsString(reportMailingJobStretchyReportParamDateOption);
+ }
+ }
+
+ reportParams.add(key, value);
+ }
+ }
+
+ // generate the report output stream, method in turn call another that sends the file to the email recipients
+ this.generateReportOutputStream(reportMailingJob, emailAttachmentFileFormat, reportParams, reportName, errorLog);
+
+ // update the previous run time, next run time, status, error log properties
+ this.updateReportMailingJobAfterJobExecution(reportMailingJob, errorLog, localDateTimeOftenant);
+ }
+ }
+ }
+ }
+
+ /**
+ * update the report mailing job entity after job execution
+ *
+ * @param reportMailingJob -- the report mailing job entity
+ * @param errorLog -- StringBuilder object containing the error log if any
+ * @param jobStartDateTime -- the start DateTime of the job
+ * @return None
+ **/
+ private void updateReportMailingJobAfterJobExecution(final ReportMailingJob reportMailingJob, final StringBuilder errorLog,
+ final DateTime jobStartDateTime) {
+ final String recurrence = reportMailingJob.getRecurrence();
+ final DateTime nextRunDateTime = reportMailingJob.getNextRunDateTime();
+ ReportMailingJobPreviousRunStatus reportMailingJobPreviousRunStatus = ReportMailingJobPreviousRunStatus.SUCCESS;
+
+ reportMailingJob.updatePreviousRunErrorLog(null);
+
+ if (errorLog != null && errorLog.length() > 0) {
+ reportMailingJobPreviousRunStatus = ReportMailingJobPreviousRunStatus.ERROR;
+ reportMailingJob.updatePreviousRunErrorLog(errorLog.toString());
+ }
+
+ reportMailingJob.increaseNumberOfRunsByOne();
+ reportMailingJob.updatePreviousRunStatus(reportMailingJobPreviousRunStatus.getValue());
+ reportMailingJob.updatePreviousRunDateTime(reportMailingJob.getNextRunDateTime());
+
+ // check if the job has a recurrence pattern, if not deactivate the job. The job will only run once
+ if (StringUtils.isEmpty(recurrence)) {
+ // deactivate job
+ reportMailingJob.deactivate();
+
+ // job will only run once, no next run time
+ reportMailingJob.updateNextRunDateTime(null);
+ } else if (nextRunDateTime != null) {
+ final DateTime nextRecurringDateTime = this.createNextRecurringDateTime(recurrence, nextRunDateTime);
+
+ // finally update the next run date time property
+ reportMailingJob.updateNextRunDateTime(nextRecurringDateTime);
+ }
+
+ // save the ReportMailingJob entity
+ this.reportMailingJobRepository.save(reportMailingJob);
+
+ // create a new report mailing job run history entity
+ this.createReportMailingJobRunHistroryAfterJobExecution(reportMailingJob, errorLog, jobStartDateTime,
+ reportMailingJobPreviousRunStatus.getValue());
+ }
+
+ /**
+ * create the next recurring DateTime from recurrence pattern, start DateTime and current DateTime
+ *
+ * @param recurrencePattern
+ * @param startDateTime
+ * @return DateTime object
+ */
+ private DateTime createNextRecurringDateTime(final String recurrencePattern, final DateTime startDateTime) {
+ DateTime nextRecurringDateTime = null;
+
+ // the recurrence pattern/rule cannot be empty
+ if (StringUtils.isNotBlank(recurrencePattern) && startDateTime != null) {
+ final LocalDate nextDayLocalDate = startDateTime.plus(1).toLocalDate();
+ final LocalDate nextRecurringLocalDate = CalendarUtils.getNextRecurringDate(recurrencePattern, startDateTime.toLocalDate(),
+ nextDayLocalDate);
+ final String nextDateTimeString = nextRecurringLocalDate + " " + startDateTime.getHourOfDay() + ":" + startDateTime.getMinuteOfHour()
+ + ":" + startDateTime.getSecondOfMinute();
+ final DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(DATETIME_FORMAT);
+
+ nextRecurringDateTime = DateTime.parse(nextDateTimeString, dateTimeFormatter);
+ }
+
+ return nextRecurringDateTime;
+ }
+
+ /**
+ * create a new report mailing job run history entity after job execution
+ *
+ * @param reportMailingJob -- the report mailing job entity
+ * @param errorLog -- StringBuilder object containing the error log if any
+ * @param jobStartDateTime -- the start DateTime of the job
+ * @param jobRunStatus -- the status of the job (success/error)
+ * @return None
+ **/
+ private void createReportMailingJobRunHistroryAfterJobExecution(final ReportMailingJob reportMailingJob, final StringBuilder errorLog,
+ final DateTime jobStartDateTime, final String jobRunStatus) {
+ final DateTime jobEndDateTime = DateUtils.getLocalDateTimeOfTenant().toDateTime();
+ final String errorLogToString = (errorLog != null) ? errorLog.toString() : null;
+ final ReportMailingJobRunHistory reportMailingJobRunHistory = ReportMailingJobRunHistory.newInstance(reportMailingJob, jobStartDateTime,
+ jobEndDateTime, jobRunStatus, null, errorLogToString);
+
+ this.reportMailingJobRunHistoryRepository.save(reportMailingJobRunHistory);
+ }
+
+ /**
+ * Handle any SQL data integrity issue
+ *
+ * @param jsonCommand -- JsonCommand object
+ * @param dve -- data integrity exception object
+ * @return None
+ **/
+ private void handleDataIntegrityIssues(final JsonCommand jsonCommand, final DataIntegrityViolationException dve) {
+ final Throwable realCause = dve.getMostSpecificCause();
+
+ if (realCause.getMessage().contains(ReportMailingJobConstants.NAME_PARAM_NAME)) {
+ final String name = jsonCommand.stringValueOfParameterNamed(ReportMailingJobConstants.NAME_PARAM_NAME);
+ throw new PlatformDataIntegrityException("error.msg.report.mailing.job.duplicate.name", "Report mailing job with name `" + name + "` already exists",
+ ReportMailingJobConstants.NAME_PARAM_NAME, name);
+ }
+
+ logger.error(dve.getMessage(), dve);
+
+ throw new PlatformDataIntegrityException("error.msg.charge.unknown.data.integrity.issue",
+ "Unknown data integrity issue with resource: " + realCause.getMessage());
+ }
+
+ /**
+ * generate the report output stream
+ *
+ * @param reportMailingJob
+ * @param emailAttachmentFileFormat
+ * @param reportParams
+ * @param reportName
+ * @param errorLog
+ * @return the error log StringBuilder object
+ */
+ private StringBuilder generateReportOutputStream(final ReportMailingJob reportMailingJob, final ReportMailingJobEmailAttachmentFileFormat emailAttachmentFileFormat,
+ final MultivaluedMap reportParams, final String reportName, final StringBuilder errorLog) {
+
+ try {
+ final String reportType = this.readReportingService.getReportType(reportName);
+ final ReportingProcessService reportingProcessService = this.reportingProcessServiceProvider.findReportingProcessService(reportType);
+
+ if (reportingProcessService != null) {
+ final Response processReport = reportingProcessService.processRequest(reportName, reportParams);
+ final Object reponseObject = (processReport != null) ? processReport.getEntity() : null;
+
+ if (reponseObject != null && reponseObject.getClass().equals(ByteArrayOutputStream.class)) {
+ final ByteArrayOutputStream byteArrayOutputStream = ByteArrayOutputStream.class.cast(reponseObject);
+ final String fileLocation = FileSystemContentRepository.FINERACT_BASE_DIR + File.separator + "";
+ final String fileNameWithoutExtension = fileLocation + File.separator + reportName;
+
+ // check if file directory exists, if not create directory
+ if (!new File(fileLocation).isDirectory()) {
+ new File(fileLocation).mkdirs();
+ }
+
+ if ((byteArrayOutputStream == null) || byteArrayOutputStream.size() == 0) {
+ errorLog.append("Report processing failed, empty output stream created");
+ } else if ((errorLog != null && errorLog.length() == 0) && (byteArrayOutputStream.size() > 0)) {
+ final String fileName = fileNameWithoutExtension + "." + emailAttachmentFileFormat.getValue();
+
+ // send the file to email recipients
+ this.sendReportFileToEmailRecipients(reportMailingJob, fileName, byteArrayOutputStream, errorLog);
+ }
+ } else {
+ errorLog.append("Response object entity is not equal to ByteArrayOutputStream ---------- ");
+ }
+ } else {
+ errorLog.append("ReportingProcessService object is null ---------- ");
+ }
+ } catch (Exception e) {
+ errorLog.append("The ReportMailingJobWritePlatformServiceImpl.generateReportOutputStream method threw an Exception: "
+ + e + " ---------- ");
+ }
+
+ return errorLog;
+ }
+
+ /**
+ * send report file to email recipients
+ *
+ * @param reportMailingJob
+ * @param fileName
+ * @param byteArrayOutputStream
+ * @param errorLog
+ */
+ private void sendReportFileToEmailRecipients(final ReportMailingJob reportMailingJob, final String fileName,
+ final ByteArrayOutputStream byteArrayOutputStream, final StringBuilder errorLog) {
+ final Set emailRecipients = this.reportMailingJobValidator.validateEmailRecipients(reportMailingJob.getEmailRecipients());
+
+ try {
+ final File file = new File(fileName);
+ final FileOutputStream outputStream = new FileOutputStream(file);
+ byteArrayOutputStream.writeTo(outputStream);
+
+ for (String emailRecipient : emailRecipients) {
+ final ReportMailingJobEmailData reportMailingJobEmailData = new ReportMailingJobEmailData(emailRecipient,
+ reportMailingJob.getEmailMessage(), reportMailingJob.getEmailSubject(), file);
+
+ this.reportMailingJobEmailService.sendEmailWithAttachment(reportMailingJobEmailData);
+ }
+
+ outputStream.close();
+
+ } catch (IOException e) {
+ errorLog.append("The ReportMailingJobWritePlatformServiceImpl.sendReportFileToEmailRecipients method threw an IOException "
+ + "exception: " + e + " ---------- ");
+ }
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/util/ReportMailingJobDateUtil.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/util/ReportMailingJobDateUtil.java
new file mode 100644
index 000000000..a1d97fa92
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/util/ReportMailingJobDateUtil.java
@@ -0,0 +1,114 @@
+/**
+ * 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.reportmailingjob.util;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+
+import org.apache.fineract.infrastructure.reportmailingjob.data.ReportMailingJobStretchyReportParamDateOption;
+
+public class ReportMailingJobDateUtil {
+ public static final String MYSQL_DATE_FORMAT = "yyyy-MM-dd";
+
+ /**
+ * get the current date as string using the mysql date format yyyy-MM-dd
+ **/
+ public static String getTodayDateAsString() {
+ // get a calendar instance, which defaults to "now"
+ Calendar calendar = Calendar.getInstance();
+
+ // get a date to represent "today"
+ Date today = calendar.getTime();
+
+ // get a SimpleDateFormat instance, passing the mysql date format as parameter
+ SimpleDateFormat simpleDateFormat = new SimpleDateFormat(MYSQL_DATE_FORMAT);
+
+ // return date as string
+ return simpleDateFormat.format(today);
+ }
+
+ /**
+ * get the yesterday's date as string using the mysql date format yyyy-MM-dd
+ **/
+ public static String getYesterdayDateAsString() {
+ // get a calendar instance, which defaults to "now"
+ Calendar calendar = Calendar.getInstance();
+
+ // add one day to the date/calendar
+ calendar.add(Calendar.DAY_OF_YEAR, -1);
+
+ // now get "yesterday"
+ Date yesterday = calendar.getTime();
+
+ // get a SimpleDateFormat instance, passing the mysql date format as parameter
+ SimpleDateFormat simpleDateFormat = new SimpleDateFormat(MYSQL_DATE_FORMAT);
+
+ // return date as string
+ return simpleDateFormat.format(yesterday);
+ }
+
+ /**
+ * get the tomorrow's date as string using the mysql date format yyyy-MM-dd
+ **/
+ public static String getTomorrowDateAsString() {
+ // get a calendar instance, which defaults to "now"
+ Calendar calendar = Calendar.getInstance();
+
+ // add one day to the date/calendar
+ calendar.add(Calendar.DAY_OF_YEAR, 1);
+
+ // now get "tomorrow"
+ Date tomorrow = calendar.getTime();
+
+ // get a SimpleDateFormat instance, passing the mysql date format as parameter
+ SimpleDateFormat simpleDateFormat = new SimpleDateFormat(MYSQL_DATE_FORMAT);
+
+ // return date as string
+ return simpleDateFormat.format(tomorrow);
+ }
+
+ /**
+ * get date as string based on the value of the {@link ReportMailingJobStretchyReportParamDateOption} object
+ *
+ * @param reportMailingJobStretchyReportParamDateOption {@link ReportMailingJobStretchyReportParamDateOption} Enum
+ **/
+ public static String getDateAsString(final ReportMailingJobStretchyReportParamDateOption reportMailingJobStretchyReportParamDateOption) {
+ String dateAsString = null;
+
+ switch (reportMailingJobStretchyReportParamDateOption) {
+ case TODAY:
+ dateAsString = getTodayDateAsString();
+ break;
+
+ case YESTERDAY:
+ dateAsString = getYesterdayDateAsString();
+ break;
+
+ case TOMORROW:
+ dateAsString = getTomorrowDateAsString();
+ break;
+
+ default:
+ break;
+ }
+
+ return dateAsString;
+ }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/validation/ReportMailingJobValidator.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/validation/ReportMailingJobValidator.java
new file mode 100644
index 000000000..c3bbaf851
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/validation/ReportMailingJobValidator.java
@@ -0,0 +1,319 @@
+/**
+ * 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.reportmailingjob.validation;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.reportmailingjob.ReportMailingJobConstants;
+import org.apache.fineract.infrastructure.reportmailingjob.data.ReportMailingJobEmailAttachmentFileFormat;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.type.TypeReference;
+import org.joda.time.LocalDateTime;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+
+@Component
+public class ReportMailingJobValidator {
+ private final FromJsonHelper fromJsonHelper;
+ private static final String EMAIL_REGEX = "^[\\w!#$%&’*+/=?`{|}~^-]+(?:\\.[\\w!#$%&’*+/=?`{|}~^-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}$";
+ private static final Pattern EMAIL_PATTERN = Pattern.compile(EMAIL_REGEX);
+
+ @Autowired
+ public ReportMailingJobValidator(final FromJsonHelper fromJsonHelper) {
+ this.fromJsonHelper = fromJsonHelper;
+ }
+
+ /**
+ * validate the request to create a new report mailing job
+ *
+ * @param jsonCommand -- the JSON command object (instance of the JsonCommand class)
+ * @return None
+ **/
+ public void validateCreateRequest(final JsonCommand jsonCommand) {
+ final String jsonString = jsonCommand.json();
+ final JsonElement jsonElement = jsonCommand.parsedJson();
+
+ if (StringUtils.isBlank(jsonString)) {
+ throw new InvalidJsonException();
+ }
+
+ final Type typeToken = new TypeToken>() {}.getType();
+ this.fromJsonHelper.checkForUnsupportedParameters(typeToken, jsonString,
+ ReportMailingJobConstants.CREATE_REQUEST_PARAMETERS);
+
+ final List dataValidationErrors = new ArrayList<>();
+ final DataValidatorBuilder dataValidatorBuilder = new DataValidatorBuilder(dataValidationErrors).
+ resource(StringUtils.lowerCase(ReportMailingJobConstants.REPORT_MAILING_JOB_RESOURCE_NAME));
+
+ final String name = this.fromJsonHelper.extractStringNamed(ReportMailingJobConstants.NAME_PARAM_NAME, jsonElement);
+ dataValidatorBuilder.reset().parameter(ReportMailingJobConstants.NAME_PARAM_NAME).value(name).notBlank().notExceedingLengthOf(100);
+
+ final String startDateTime = this.fromJsonHelper.extractStringNamed(ReportMailingJobConstants.START_DATE_TIME_PARAM_NAME,
+ jsonElement);
+ dataValidatorBuilder.reset().parameter(ReportMailingJobConstants.START_DATE_TIME_PARAM_NAME).value(startDateTime).notBlank();
+
+ final Integer stretchyReportId = this.fromJsonHelper.extractIntegerWithLocaleNamed(ReportMailingJobConstants.STRETCHY_REPORT_ID_PARAM_NAME,
+ jsonElement);
+ dataValidatorBuilder.reset().parameter(ReportMailingJobConstants.STRETCHY_REPORT_ID_PARAM_NAME).value(stretchyReportId).notNull().
+ integerGreaterThanZero();
+
+ final String emailRecipients = this.fromJsonHelper.extractStringNamed(ReportMailingJobConstants.EMAIL_RECIPIENTS_PARAM_NAME, jsonElement);
+ dataValidatorBuilder.reset().parameter(ReportMailingJobConstants.EMAIL_RECIPIENTS_PARAM_NAME).value(emailRecipients).notBlank();
+
+ final String emailSubject = this.fromJsonHelper.extractStringNamed(ReportMailingJobConstants.EMAIL_SUBJECT_PARAM_NAME, jsonElement);
+ dataValidatorBuilder.reset().parameter(ReportMailingJobConstants.EMAIL_SUBJECT_PARAM_NAME).value(emailSubject).notBlank().notExceedingLengthOf(100);
+
+ final String emailMessage = this.fromJsonHelper.extractStringNamed(ReportMailingJobConstants.EMAIL_MESSAGE_PARAM_NAME, jsonElement);
+ dataValidatorBuilder.reset().parameter(ReportMailingJobConstants.EMAIL_MESSAGE_PARAM_NAME).value(emailMessage).notBlank();
+
+ if (this.fromJsonHelper.parameterExists(ReportMailingJobConstants.IS_ACTIVE_PARAM_NAME, jsonElement)) {
+ final Boolean isActive = this.fromJsonHelper.extractBooleanNamed(ReportMailingJobConstants.IS_ACTIVE_PARAM_NAME,
+ jsonElement);
+ dataValidatorBuilder.reset().parameter(ReportMailingJobConstants.IS_ACTIVE_PARAM_NAME).value(isActive).notNull();
+ }
+
+ final Integer emailAttachmentFileFormatId = this.fromJsonHelper.extractIntegerSansLocaleNamed(
+ ReportMailingJobConstants.EMAIL_ATTACHMENT_FILE_FORMAT_ID_PARAM_NAME, jsonElement);
+ dataValidatorBuilder.reset().parameter(ReportMailingJobConstants.EMAIL_ATTACHMENT_FILE_FORMAT_ID_PARAM_NAME).
+ value(emailAttachmentFileFormatId).notNull();
+
+ if (emailAttachmentFileFormatId != null) {
+ dataValidatorBuilder.reset().parameter(ReportMailingJobConstants.EMAIL_ATTACHMENT_FILE_FORMAT_ID_PARAM_NAME).value(emailAttachmentFileFormatId).
+ isOneOfTheseValues(ReportMailingJobEmailAttachmentFileFormat.validIds());
+ }
+
+ final String dateFormat = jsonCommand.dateFormat();
+ dataValidatorBuilder.reset().parameter(ReportMailingJobConstants.DATE_FORMAT_PARAM_NAME).value(dateFormat).notBlank();
+
+ if (StringUtils.isNotEmpty(dateFormat)) {
+
+ try {
+ final DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(dateFormat).withLocale(jsonCommand.extractLocale());
+
+ // try to parse the date time string
+ LocalDateTime.parse(startDateTime, dateTimeFormatter);
+ }
+
+ catch(IllegalArgumentException ex) {
+ dataValidatorBuilder.reset().parameter(ReportMailingJobConstants.DATE_FORMAT_PARAM_NAME).value(dateFormat).failWithCode("invalid.date.format");
+ }
+ }
+
+ throwExceptionIfValidationWarningsExist(dataValidationErrors);
+ }
+
+ /**
+ * validate the request to update a report mailing job
+ *
+ * @param jsonCommand -- the JSON command object (instance of the JsonCommand class)
+ * @return None
+ **/
+ public void validateUpdateRequest(final JsonCommand jsonCommand) {
+ final String jsonString = jsonCommand.json();
+ final JsonElement jsonElement = jsonCommand.parsedJson();
+
+ if (StringUtils.isBlank(jsonString)) {
+ throw new InvalidJsonException();
+ }
+
+ final Type typeToken = new TypeToken>() {}.getType();
+ this.fromJsonHelper.checkForUnsupportedParameters(typeToken, jsonString,
+ ReportMailingJobConstants.UPDATE_REQUEST_PARAMETERS);
+
+ final List dataValidationErrors = new ArrayList<>();
+ final DataValidatorBuilder dataValidatorBuilder = new DataValidatorBuilder(dataValidationErrors).
+ resource(StringUtils.lowerCase(ReportMailingJobConstants.REPORT_MAILING_JOB_RESOURCE_NAME));
+
+ if (this.fromJsonHelper.parameterExists(ReportMailingJobConstants.NAME_PARAM_NAME, jsonElement)) {
+ final String name = this.fromJsonHelper.extractStringNamed(ReportMailingJobConstants.NAME_PARAM_NAME, jsonElement);
+ dataValidatorBuilder.reset().parameter(ReportMailingJobConstants.NAME_PARAM_NAME).value(name).notBlank().notExceedingLengthOf(100);
+ }
+
+ if (this.fromJsonHelper.parameterExists(ReportMailingJobConstants.STRETCHY_REPORT_ID_PARAM_NAME, jsonElement)) {
+ final Integer stretchyReportId = this.fromJsonHelper.extractIntegerWithLocaleNamed(ReportMailingJobConstants.STRETCHY_REPORT_ID_PARAM_NAME,
+ jsonElement);
+ dataValidatorBuilder.reset().parameter(ReportMailingJobConstants.STRETCHY_REPORT_ID_PARAM_NAME).value(stretchyReportId).notNull().
+ integerGreaterThanZero();
+ }
+
+ if (this.fromJsonHelper.parameterExists(ReportMailingJobConstants.EMAIL_RECIPIENTS_PARAM_NAME, jsonElement)) {
+ final String emailRecipients = this.fromJsonHelper.extractStringNamed(ReportMailingJobConstants.EMAIL_RECIPIENTS_PARAM_NAME, jsonElement);
+ dataValidatorBuilder.reset().parameter(ReportMailingJobConstants.EMAIL_RECIPIENTS_PARAM_NAME).value(emailRecipients).notBlank();
+ }
+
+ if (this.fromJsonHelper.parameterExists(ReportMailingJobConstants.EMAIL_SUBJECT_PARAM_NAME, jsonElement)) {
+ final String emailSubject = this.fromJsonHelper.extractStringNamed(ReportMailingJobConstants.EMAIL_SUBJECT_PARAM_NAME, jsonElement);
+ dataValidatorBuilder.reset().parameter(ReportMailingJobConstants.EMAIL_SUBJECT_PARAM_NAME).value(emailSubject).notBlank().notExceedingLengthOf(100);
+ }
+
+ if (this.fromJsonHelper.parameterExists(ReportMailingJobConstants.EMAIL_MESSAGE_PARAM_NAME, jsonElement)) {
+ final String emailMessage = this.fromJsonHelper.extractStringNamed(ReportMailingJobConstants.EMAIL_MESSAGE_PARAM_NAME, jsonElement);
+ dataValidatorBuilder.reset().parameter(ReportMailingJobConstants.EMAIL_MESSAGE_PARAM_NAME).value(emailMessage).notBlank();
+ }
+
+ if (this.fromJsonHelper.parameterExists(ReportMailingJobConstants.IS_ACTIVE_PARAM_NAME, jsonElement)) {
+ final Boolean isActive = this.fromJsonHelper.extractBooleanNamed(ReportMailingJobConstants.IS_ACTIVE_PARAM_NAME,
+ jsonElement);
+ dataValidatorBuilder.reset().parameter(ReportMailingJobConstants.IS_ACTIVE_PARAM_NAME).value(isActive).notNull();
+ }
+
+ if (this.fromJsonHelper.parameterExists(ReportMailingJobConstants.EMAIL_ATTACHMENT_FILE_FORMAT_ID_PARAM_NAME, jsonElement)) {
+ final Integer emailAttachmentFileFormatId = this.fromJsonHelper.extractIntegerSansLocaleNamed(
+ ReportMailingJobConstants.EMAIL_ATTACHMENT_FILE_FORMAT_ID_PARAM_NAME, jsonElement);
+ dataValidatorBuilder.reset().parameter(ReportMailingJobConstants.EMAIL_ATTACHMENT_FILE_FORMAT_ID_PARAM_NAME).
+ value(emailAttachmentFileFormatId).notNull();
+
+ if (emailAttachmentFileFormatId != null) {
+ dataValidatorBuilder.reset().parameter(ReportMailingJobConstants.EMAIL_ATTACHMENT_FILE_FORMAT_ID_PARAM_NAME).value(emailAttachmentFileFormatId).
+ isOneOfTheseValues(ReportMailingJobEmailAttachmentFileFormat.validIds());
+ }
+ }
+
+ if (this.fromJsonHelper.parameterExists(ReportMailingJobConstants.START_DATE_TIME_PARAM_NAME, jsonElement)) {
+ final String dateFormat = jsonCommand.dateFormat();
+ dataValidatorBuilder.reset().parameter(ReportMailingJobConstants.DATE_FORMAT_PARAM_NAME).value(dateFormat).notBlank();
+
+ final String startDateTime = this.fromJsonHelper.extractStringNamed(ReportMailingJobConstants.START_DATE_TIME_PARAM_NAME,
+ jsonElement);
+ dataValidatorBuilder.reset().parameter(ReportMailingJobConstants.START_DATE_TIME_PARAM_NAME).value(startDateTime).notBlank();
+
+ if (StringUtils.isNotEmpty(dateFormat)) {
+
+ try {
+ final DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(dateFormat).withLocale(jsonCommand.extractLocale());
+
+ // try to parse the date time string
+ LocalDateTime.parse(startDateTime, dateTimeFormatter);
+ }
+
+ catch(IllegalArgumentException ex) {
+ dataValidatorBuilder.reset().parameter(ReportMailingJobConstants.DATE_FORMAT_PARAM_NAME).value(dateFormat).failWithCode("invalid.date.format");
+ }
+ }
+ }
+
+ throwExceptionIfValidationWarningsExist(dataValidationErrors);
+ }
+
+ /**
+ * check if string is a valid email address
+ *
+ * @param email -- string to be validated
+ * @return true if string is a valid email address
+ **/
+ public boolean isValidEmail(String email) {
+ // this is the easiest check
+ if (email == null) {
+ return false;
+ }
+
+ // this is another easy check
+ if (email.endsWith(".")) {
+ return false;
+ }
+
+ // Check the whole email address structure
+ Matcher emailMatcher = EMAIL_PATTERN.matcher(email);
+
+ // check if the Matcher matches the email pattern
+ if (!emailMatcher.matches()) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Validate the email recipients string
+ *
+ * @param emailRecipients -- the email recipients string to be validated
+ * @return a hashset containing valid email addresses
+ **/
+ public Set validateEmailRecipients(String emailRecipients) {
+ Set emailRecipientsSet = new HashSet<>();
+
+ if (emailRecipients != null) {
+ String[] split = emailRecipients.split(",");
+
+ for (String emailAddress : split) {
+ emailAddress = emailAddress.trim();
+
+ if (this.isValidEmail(emailAddress)) {
+ emailRecipientsSet.add(emailAddress);
+ }
+ }
+ }
+
+ return emailRecipientsSet;
+ }
+
+ /**
+ * validate the stretchy report param json string
+ *
+ * @param stretchyReportParamMap -- json string to be validated
+ * @return if string is valid or empty, a HashMap object, else null
+ **/
+ public HashMap validateStretchyReportParamMap(String stretchyReportParamMap) {
+ HashMap stretchyReportParamHashMap = new HashMap<>();
+
+ if (!StringUtils.isEmpty(stretchyReportParamMap)) {
+ try {
+ stretchyReportParamHashMap = new ObjectMapper().readValue(stretchyReportParamMap, new TypeReference>(){});
+ }
+
+ catch(Exception e) {
+ stretchyReportParamHashMap = null;
+ }
+ }
+
+ return stretchyReportParamHashMap;
+ }
+
+ /**
+ * throw a PlatformApiDataValidationException exception if there are validation errors
+ *
+ * @param dataValidationErrors -- list of ApiParameterError objects
+ * @return None
+ **/
+ private void throwExceptionIfValidationWarningsExist(final List dataValidationErrors) {
+ if (!dataValidationErrors.isEmpty()) {
+ throw new PlatformApiDataValidationException(dataValidationErrors);
+ }
+ }
+}
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V312__report_mailing_job_module.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V312__report_mailing_job_module.sql
new file mode 100644
index 000000000..155c77a15
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V312__report_mailing_job_module.sql
@@ -0,0 +1,61 @@
+create table if not exists m_report_mailing_job (
+id bigint primary key auto_increment,
+name varchar(100) not null,
+description text null,
+start_datetime datetime not null,
+recurrence varchar(100) null,
+created_date date not null,
+createdby_id bigint not null,
+lastmodified_date date null,
+lastmodifiedby_id bigint null,
+email_recipients text not null,
+email_subject varchar(100) not null,
+email_message text not null,
+email_attachment_file_format varchar(10) not null,
+stretchy_report_id int not null,
+stretchy_report_param_map text null,
+previous_run_datetime datetime null,
+next_run_datetime datetime null,
+previous_run_status varchar(10) null,
+previous_run_error_log text null,
+previous_run_error_message text null,
+number_of_runs int not null default 0,
+is_active tinyint(1) not null default 0,
+is_deleted tinyint(1) not null default 0,
+run_as_userid bigint not null,
+foreign key (createdby_id) references m_appuser(id),
+foreign key (lastmodifiedby_id) references m_appuser(id),
+foreign key (stretchy_report_id) references stretchy_report(id),
+foreign key (run_as_userid) references m_appuser(id),
+constraint unique_name unique (name)
+);
+
+create table if not exists m_report_mailing_job_run_history (
+id bigint primary key auto_increment,
+job_id bigint not null,
+start_datetime datetime not null,
+end_datetime datetime not null,
+status varchar(10) not null,
+error_message text null,
+error_log text null,
+foreign key (job_id) references m_report_mailing_job (id)
+);
+
+create table if not exists m_report_mailing_job_configuration (
+id int primary key auto_increment,
+name varchar(50) not null,
+`value` varchar(200) not null,
+constraint unique_name unique (name)
+);
+
+insert into m_permission (`grouping`, code, entity_name, action_name, can_maker_checker)
+values ('jobs', 'CREATE_REPORTMAILINGJOB', 'REPORTMAILINGJOB', 'CREATE', 0),
+('jobs', 'UPDATE_REPORTMAILINGJOB', 'REPORTMAILINGJOB', 'UPDATE', 0),
+('jobs', 'DELETE_REPORTMAILINGJOB', 'REPORTMAILINGJOB', 'DELETE', 0),
+('jobs', 'READ_REPORTMAILINGJOB', 'REPORTMAILINGJOB', 'READ', 0);
+
+insert into m_report_mailing_job_configuration (name, `value`)
+values ('GMAIL_SMTP_SERVER', 'smtp.gmail.com'), ('GMAIL_SMTP_PORT', 587), ('GMAIL_SMTP_USERNAME', ''), ('GMAIL_SMTP_PASSWORD', '');
+
+insert into job (name, display_name, cron_expression, create_time)
+values ('Execute Report Mailing Jobs', 'Execute Report Mailing Jobs', '0 0/15 * * * ?', NOW());