fineract-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From nazeer1100...@apache.org
Subject [1/5] incubator-fineract git commit: commit for FINERACT-65 (Implement ability to schedule & e-mail reports)
Date Fri, 12 Aug 2016 03:39:53 GMT
Repository: incubator-fineract
Updated Branches:
  refs/heads/develop f8f3f743c -> 47f21bc8d


http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/b30976bb/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobReadPlatformServiceImpl.java
----------------------------------------------------------------------
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 0000000..afec180
--- /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<ReportMailingJobData> retrieveAllReportMailingJobs(final SearchParameters searchParameters) {
+        final StringBuilder sqlStringBuilder = new StringBuilder(200);
+        final List<Object> queryParameters = new ArrayList<>();
+        final ReportMailingJobMapper mapper = new ReportMailingJobMapper();
+        final PaginationHelper<ReportMailingJobData> 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<ReportMailingJobData> 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<EnumOptionData> emailAttachmentFileFormatOptions = ReportMailingJobEmailAttachmentFileFormat.validOptions();
+        final List<EnumOptionData> stretchyReportParamDateOptions = ReportMailingJobStretchyReportParamDateOption.validOptions();
+        
+        return ReportMailingJobData.newInstance(emailAttachmentFileFormatOptions, stretchyReportParamDateOptions);
+    }
+    
+    private static final class ReportMailingJobMapper implements RowMapper<ReportMailingJobData> {
+        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);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/b30976bb/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobRunHistoryReadPlatformService.java
----------------------------------------------------------------------
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 0000000..9f4a204
--- /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<ReportMailingJobRunHistoryData> retrieveRunHistoryByJobId(Long reportMailingJobId, SearchParameters searchParameters);
+}

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/b30976bb/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobRunHistoryReadPlatformServiceImpl.java
----------------------------------------------------------------------
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 0000000..4aeb68f
--- /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<ReportMailingJobRunHistoryData> paginationHelper = new PaginationHelper<>();
+    
+    @Autowired
+    public ReportMailingJobRunHistoryReadPlatformServiceImpl(final RoutingDataSource dataSource) {
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.reportMailingJobRunHistoryMapper = new ReportMailingJobRunHistoryMapper();
+    }
+    
+    @Override
+    public Page<ReportMailingJobRunHistoryData> retrieveRunHistoryByJobId(final Long reportMailingJobId, 
+            final SearchParameters searchParameters) {
+        final StringBuilder sqlStringBuilder = new StringBuilder(200);
+        final List<Object> 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<ReportMailingJobRunHistoryData> {
+        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);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/b30976bb/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobWritePlatformService.java
----------------------------------------------------------------------
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 0000000..ce54919
--- /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;
+}

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/b30976bb/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/service/ReportMailingJobWritePlatformServiceImpl.java
----------------------------------------------------------------------
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 0000000..02972a9
--- /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<String, Object> 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<ReportMailingJob> 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<String, String> validateStretchyReportParamMap = this.reportMailingJobValidator.
+                            validateStretchyReportParamMap(reportMailingJob.getStretchyReportParamMap());
+                    MultivaluedMap<String, String> reportParams = new MultivaluedMapImpl();
+                    
+                    if (validateStretchyReportParamMap != null) {
+                        Iterator<Map.Entry<String, String>> validateStretchyReportParamMapEntries = validateStretchyReportParamMap.entrySet().iterator();
+                        
+                        while (validateStretchyReportParamMapEntries.hasNext()) {
+                            Map.Entry<String, String> 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<String, String> 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<String> 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 + " ---------- ");
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/b30976bb/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/util/ReportMailingJobDateUtil.java
----------------------------------------------------------------------
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 0000000..a1d97fa
--- /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;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/b30976bb/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/validation/ReportMailingJobValidator.java
----------------------------------------------------------------------
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 0000000..c3bbaf8
--- /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<Map<String, Object>>() {}.getType();
+        this.fromJsonHelper.checkForUnsupportedParameters(typeToken, jsonString,
+                ReportMailingJobConstants.CREATE_REQUEST_PARAMETERS);
+        
+        final List<ApiParameterError> 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<Map<String, Object>>() {}.getType();
+        this.fromJsonHelper.checkForUnsupportedParameters(typeToken, jsonString,
+                ReportMailingJobConstants.UPDATE_REQUEST_PARAMETERS);
+        
+        final List<ApiParameterError> 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<String> validateEmailRecipients(String emailRecipients) {
+        Set<String> 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<String,String> validateStretchyReportParamMap(String stretchyReportParamMap) {
+        HashMap<String,String> stretchyReportParamHashMap = new HashMap<>();
+        
+        if (!StringUtils.isEmpty(stretchyReportParamMap)) {
+            try {
+                stretchyReportParamHashMap = new ObjectMapper().readValue(stretchyReportParamMap, new TypeReference<HashMap<String,String>>(){});
+            }
+            
+            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<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) { 
+            throw new PlatformApiDataValidationException(dataValidationErrors); 
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/b30976bb/fineract-provider/src/main/resources/sql/migrations/core_db/V312__report_mailing_job_module.sql
----------------------------------------------------------------------
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 0000000..155c77a
--- /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());


Mime
View raw message