fineract-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From nazeer1100...@apache.org
Subject [2/7] fineract git commit: Email Campaigns PR
Date Mon, 20 Nov 2017 13:33:53 GMT
http://git-wip-us.apache.org/repos/asf/fineract/blob/c338c175/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/SearchParameters.java
----------------------------------------------------------------------
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 34d397f..a3b14fb 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
@@ -225,6 +225,21 @@ public final class SearchParameters {
                 staffId, accountNo, loanId, savingsId, orphansOnly, isSelfUser);
     }
 
+    public static SearchParameters forEmailCampaign(final String sqlSearch, final Integer offset, final Integer limit, final String orderBy, final String sortOrder) {
+
+        final String externalId = null;
+        final Integer maxLimitAllowed = getCheckedLimit(limit);
+        final Long staffId = null;
+        final String accountNo = null;
+        final Long loanId = null;
+        final Long savingsId = null;
+        final Boolean orphansOnly = false;
+        final boolean isSelfUser = false;
+
+        return new SearchParameters(sqlSearch, null, externalId, null, null, null, null, offset, maxLimitAllowed, orderBy, sortOrder,
+                staffId, accountNo, loanId, savingsId, orphansOnly, isSelfUser);
+    }
+	
     private SearchParameters(final String sqlSearch, final Long officeId, final String externalId, final String name,
             final String hierarchy, final String firstname, final String lastname, final Integer offset, final Integer limit,
             final String orderBy, final String sortOrder, final Long staffId, final String accountNo, final Long loanId,

http://git-wip-us.apache.org/repos/asf/fineract/blob/c338c175/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/domain/Report.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/domain/Report.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/domain/Report.java
index e39a591..f909c33 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/domain/Report.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/domain/Report.java
@@ -276,4 +276,8 @@ public final class Report extends AbstractPersistableCustom<Long> {
 
         return false;
     }
+	
+	public Set<ReportParameterUsage> getReportParameterUsages() {
+        return this.reportParameterUsages;
+    }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/fineract/blob/c338c175/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/domain/ReportParameterUsage.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/domain/ReportParameterUsage.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/domain/ReportParameterUsage.java
index 4d10fef..7228e9c 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/domain/ReportParameterUsage.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/domain/ReportParameterUsage.java
@@ -86,4 +86,8 @@ public final class ReportParameterUsage extends AbstractPersistableCustom<Long>
     public void updateParameterName(final String parameterName) {
         this.reportParameterName = parameterName;
     }
+	
+	public String getReportParameterName() {
+		return this.reportParameterName;
+	}
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/fineract/blob/c338c175/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/domain/ReportParameterUsageRepository.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/domain/ReportParameterUsageRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/domain/ReportParameterUsageRepository.java
index 8a49331..2d47db2 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/domain/ReportParameterUsageRepository.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/domain/ReportParameterUsageRepository.java
@@ -18,10 +18,14 @@
  */
 package org.apache.fineract.infrastructure.dataqueries.domain;
 
+import java.util.List;
+
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
 
 public interface ReportParameterUsageRepository extends JpaRepository<ReportParameterUsage, Long>,
         JpaSpecificationExecutor<ReportParameterUsage> {
     // no added behaviour
+	
+	List<ReportParameterUsage> findByReport(Report report);
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/fineract/blob/c338c175/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingService.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingService.java
index a21f645..952a36a 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingService.java
@@ -18,7 +18,9 @@
  */
 package org.apache.fineract.infrastructure.dataqueries.service;
 
+import java.io.ByteArrayOutputStream;
 import java.util.Collection;
+import java.util.Locale;
 import java.util.Map;
 
 import javax.ws.rs.core.StreamingOutput;
@@ -26,6 +28,7 @@ import javax.ws.rs.core.StreamingOutput;
 import org.apache.fineract.infrastructure.dataqueries.data.GenericResultsetData;
 import org.apache.fineract.infrastructure.dataqueries.data.ReportData;
 import org.apache.fineract.infrastructure.dataqueries.data.ReportParameterData;
+import org.apache.fineract.useradministration.domain.AppUser;
 
 public interface ReadReportingService {
 
@@ -45,8 +48,11 @@ public interface ReadReportingService {
 
     Collection<String> getAllowedReportTypes();
     
-  //needed for smsCampaign jobs where securityContext is null
-    GenericResultsetData retrieveGenericResultSetForSmsCampaign(String name, String type, Map<String, String> extractedQueryParams);
-    
-    String  sqlToRunForSmsCampaign(String name, String type, Map<String, String> queryParams);
+  //needed for smsCampaign and emailCampaign jobs where securityContext is null
+    GenericResultsetData retrieveGenericResultSetForSmsEmailCampaign(String name, String type, Map<String, String> extractedQueryParams);
+
+    String  sqlToRunForSmsEmailCampaign(String name, String type, Map<String, String> queryParams);
+
+	ByteArrayOutputStream generatePentahoReportAsOutputStream(String reportName, String outputTypeParam,
+            Map<String, String> queryParams, Locale locale, AppUser runReportAsUser, StringBuilder errorLog);
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/fineract/blob/c338c175/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingServiceImpl.java
----------------------------------------------------------------------
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 f25c371..da0bc01 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
@@ -28,6 +28,7 @@ import java.sql.SQLException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 
@@ -48,6 +49,16 @@ import org.apache.fineract.infrastructure.documentmanagement.contentrepository.F
 import org.apache.fineract.infrastructure.report.provider.ReportingProcessServiceProvider;
 import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
 import org.apache.fineract.useradministration.domain.AppUser;
+import org.pentaho.reporting.engine.classic.core.DefaultReportEnvironment;
+import org.pentaho.reporting.engine.classic.core.MasterReport;
+import org.pentaho.reporting.engine.classic.core.ReportProcessingException;
+import org.pentaho.reporting.engine.classic.core.modules.output.pageable.pdf.PdfReportUtil;
+import org.pentaho.reporting.engine.classic.core.modules.output.table.csv.CSVReportUtil;
+import org.pentaho.reporting.engine.classic.core.modules.output.table.html.HtmlReportUtil;
+import org.pentaho.reporting.engine.classic.core.modules.output.table.xls.ExcelReportUtil;
+import org.pentaho.reporting.libraries.resourceloader.Resource;
+import org.pentaho.reporting.libraries.resourceloader.ResourceException;
+import org.pentaho.reporting.libraries.resourceloader.ResourceManager;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -486,11 +497,11 @@ public class ReadReportingServiceImpl implements ReadReportingService {
     }
 
     @Override
-    public GenericResultsetData retrieveGenericResultSetForSmsCampaign(String name, String type, Map<String, String> queryParams) {
+    public GenericResultsetData retrieveGenericResultSetForSmsEmailCampaign(String name, String type, Map<String, String> queryParams) {
         final long startTime = System.currentTimeMillis();
         logger.info("STARTING REPORT: " + name + "   Type: " + type);
 
-        final String sql = sqlToRunForSmsCampaign(name, type, queryParams);
+        final String sql = sqlToRunForSmsEmailCampaign(name, type, queryParams);
 
         final GenericResultsetData result = this.genericDataService.fillGenericResultSet(sql);
 
@@ -500,7 +511,7 @@ public class ReadReportingServiceImpl implements ReadReportingService {
     }
     
     @Override
-    public String sqlToRunForSmsCampaign(final String name, final String type, final Map<String, String> queryParams) {
+    public String sqlToRunForSmsEmailCampaign(final String name, final String type, final Map<String, String> queryParams) {
         String sql = getSql(name, type);
 
         final Set<String> keys = queryParams.keySet();
@@ -515,4 +526,81 @@ public class ReadReportingServiceImpl implements ReadReportingService {
 
         return sql;
     }
+
+    @Override
+    public ByteArrayOutputStream generatePentahoReportAsOutputStream(final String reportName, final String outputTypeParam, final Map<String, String> queryParams,
+            final Locale locale, final AppUser runReportAsUser, final StringBuilder errorLog) {
+        String outputType = "HTML";
+        if (StringUtils.isNotBlank(outputTypeParam)) {
+            outputType = outputTypeParam;
+        }
+
+        if (!(outputType.equalsIgnoreCase("HTML") || outputType.equalsIgnoreCase("PDF") || outputType.equalsIgnoreCase("XLS") || outputType
+                .equalsIgnoreCase("CSV"))) { throw new PlatformDataIntegrityException("error.msg.invalid.outputType",
+                "No matching Output Type: " + outputType); }
+
+        if (this.noPentaho) { throw new PlatformDataIntegrityException("error.msg.no.pentaho", "Pentaho is not enabled",
+                "Pentaho is not enabled"); }
+
+        final String reportPath = FileSystemContentRepository.FINERACT_BASE_DIR + File.separator + "pentahoReports" + File.separator
+                + reportName + ".prpt";
+        logger.info("Report path: " + reportPath);
+
+        // load report definition
+        final ResourceManager manager = new ResourceManager();
+        manager.registerDefaults();
+        Resource res;
+
+        try {
+            res = manager.createDirectly(reportPath, MasterReport.class);
+            final MasterReport masterReport = (MasterReport) res.getResource();
+            final DefaultReportEnvironment reportEnvironment = (DefaultReportEnvironment) masterReport.getReportEnvironment();
+            
+            if (locale != null) {
+                reportEnvironment.setLocale(locale);
+            }
+            addParametersToReport(masterReport, queryParams, runReportAsUser, errorLog);
+
+            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+            if ("PDF".equalsIgnoreCase(outputType)) {
+                PdfReportUtil.createPDF(masterReport, baos);
+                return baos;
+            }
+
+            if ("XLS".equalsIgnoreCase(outputType)) {
+                ExcelReportUtil.createXLS(masterReport, baos);
+                return baos;
+            }
+
+            if ("CSV".equalsIgnoreCase(outputType)) {
+                CSVReportUtil.createCSV(masterReport, baos, "UTF-8");
+                return baos;
+            }
+
+            if ("HTML".equalsIgnoreCase(outputType)) {
+                HtmlReportUtil.createStreamHTML(masterReport, baos);
+                return baos;
+            }
+            
+        } catch (final ResourceException e) {
+            errorLog.append("ReadReportingServiceImpl.generatePentahoReportAsOutputStream method threw a Pentaho ResourceException "
+                    + "exception: " + e.getMessage() + " ---------- ");
+            throw new PlatformDataIntegrityException("error.msg.reporting.error", e.getMessage());
+        } catch (final ReportProcessingException e) {
+            errorLog.append("ReadReportingServiceImpl.generatePentahoReportAsOutputStream method threw a Pentaho ReportProcessingException "
+                    + "exception: " + e.getMessage() + " ---------- ");
+            throw new PlatformDataIntegrityException("error.msg.reporting.error", e.getMessage());
+        } catch (final IOException e) {
+            errorLog.append("ReadReportingServiceImpl.generatePentahoReportAsOutputStream method threw an IOException "
+                    + "exception: " + e.getMessage() + " ---------- ");
+            throw new PlatformDataIntegrityException("error.msg.reporting.error", e.getMessage());
+        }
+
+        errorLog.append("ReadReportingServiceImpl.generatePentahoReportAsOutputStream method threw a PlatformDataIntegrityException "
+                + "exception: No matching Output Type: " + outputType + " ---------- ");
+        throw new PlatformDataIntegrityException("error.msg.invalid.outputType", "No matching Output Type: " + outputType);
+    }
 }
+
+

http://git-wip-us.apache.org/repos/asf/fineract/blob/c338c175/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java
----------------------------------------------------------------------
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 f3a160f..19ad8e1 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
@@ -46,7 +46,11 @@ public enum JobName {
     UPDATE_SMS_OUTBOUND_WITH_CAMPAIGN_MESSAGE("Update SMS Outbound with Campaign Message"),
     SEND_MESSAGES_TO_SMS_GATEWAY("Send Messages to SMS Gateway"), 
     GET_DELIVERY_REPORTS_FROM_SMS_GATEWAY("Get Delivery Reports from SMS Gateway"),
-	GENERATE_ADHOCCLIENT_SCEHDULE("Generate AdhocClient Schedule");
+	GENERATE_ADHOCCLIENT_SCEHDULE("Generate AdhocClient Schedule"),
+	SEND_MESSAGES_TO_EMAIL_GATEWAY("Send messages to Email gateway"),
+    UPDATE_EMAIL_OUTBOUND_WITH_CAMPAIGN_MESSAGE("Update Email Outbound with campaign message"),
+    EXECUTE_EMAIL("Execute Email");
+
     private final String name;
 
     private JobName(final String name) {

http://git-wip-us.apache.org/repos/asf/fineract/blob/c338c175/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/domain/ReportMailingJobEmailAttachmentFileFormat.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/domain/ReportMailingJobEmailAttachmentFileFormat.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/domain/ReportMailingJobEmailAttachmentFileFormat.java
new file mode 100644
index 0000000..ef24a20
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/domain/ReportMailingJobEmailAttachmentFileFormat.java
@@ -0,0 +1,110 @@
+/**
+ * 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;
+
+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;
+    }
+    
+    public static ReportMailingJobEmailAttachmentFileFormat instance(final String value) {
+        ReportMailingJobEmailAttachmentFileFormat emailAttachmentFileFormat = INVALID;
+        
+        switch (value) {
+            case "xls":
+                emailAttachmentFileFormat = XLS;
+                break;
+                
+            case "pdf":
+                emailAttachmentFileFormat = PDF;
+                break;
+                
+            case "csv":
+                emailAttachmentFileFormat = CSV;
+                break;
+                
+            default:
+                break;
+        }
+        
+        return emailAttachmentFileFormat;
+    }
+    
+    public static ReportMailingJobEmailAttachmentFileFormat instance(final Integer id) {
+        ReportMailingJobEmailAttachmentFileFormat emailAttachmentFileFormat = INVALID;
+        
+        switch (id) {
+            case 1:
+                emailAttachmentFileFormat = XLS;
+                break;
+                
+            case 2:
+                emailAttachmentFileFormat = PDF;
+                break;
+                
+            case 3:
+                emailAttachmentFileFormat = CSV;
+                break;
+                
+            default:
+                break;
+        }
+        
+        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 list of valid ReportMailingJobEmailAttachmentFileFormat ids
+     **/
+    public static Object[] validValues() {
+        return new Object[] { XLS.getId(), PDF.getId(), CSV.getId() };
+    }
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/c338c175/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/helper/IPv4Helper.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/helper/IPv4Helper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/helper/IPv4Helper.java
new file mode 100644
index 0000000..2281416
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/reportmailingjob/helper/IPv4Helper.java
@@ -0,0 +1,143 @@
+/**
+ * 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.helper;
+
+import java.net.InetAddress;
+
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * This utility provides methods to either convert an IPv4 address to its long format or a 32bit dotted format.
+ *
+ * @see http://hawkee.com/snippet/9731/
+ */
+public class IPv4Helper {
+    /**
+     * Returns the long format of the provided IP address.
+     *
+     * @param ipAddress the IP address
+     * @return the long format of <code>ipAddress</code>
+     * @throws IllegalArgumentException if <code>ipAddress</code> is invalid
+     */
+    public static long ipAddressToLong(String ipAddress) {
+        if (ipAddress == null || ipAddress.isEmpty()) {
+            throw new IllegalArgumentException("ip address cannot be null or empty");
+        }
+        
+        String[] octets = ipAddress.split(java.util.regex.Pattern.quote("."));
+        
+        if (octets.length != 4) {
+            throw new IllegalArgumentException("invalid ip address");
+        }
+        
+        long ip = 0;
+        
+        for (int i = 3; i >= 0; i--) {
+            long octet = Long.parseLong(octets[3 - i]);
+            
+            if (octet > 255 || octet < 0) {
+                throw new IllegalArgumentException("invalid ip address");
+            }
+            
+            ip |= octet << (i * 8);
+        }
+        
+        return ip;
+    }
+
+    /**
+     * Returns the 32bit dotted format of the provided long ip.
+     *
+     * @param ip the long ip
+     * @return the 32bit dotted format of <code>ip</code>
+     * @throws IllegalArgumentException if <code>ip</code> is invalid
+     */
+    public static String longToIpAddress(long ip) {
+        // if ip is bigger than 255.255.255.255 or smaller than 0.0.0.0
+        if (ip > 4294967295l || ip < 0) {
+            throw new IllegalArgumentException("invalid ip");
+        }
+        
+        StringBuilder ipAddress = new StringBuilder();
+        
+        for (int i = 3; i >= 0; i--) {
+            int shift = i * 8;
+            ipAddress.append((ip & (0xff << shift)) >> shift);
+            if (i > 0) {
+                ipAddress.append(".");
+            }
+        }
+        
+        return ipAddress.toString();
+    }
+    
+    /** 
+     * check if an IP Address is within a given range of IP Addresses
+     * 
+     * @param ipAddress -- the IP Address to be checked
+     * @param startOfRange -- the first IP address in the range
+     * @param endOfRange -- the last IP address in the range
+     * @return boolean true if IP address is in the range of IP addresses
+     **/
+    public static boolean ipAddressIsInRange(final String ipAddress, final String startOfRange, final String endOfRange) {
+        final long ipAddressToLong = ipAddressToLong(ipAddress);
+        final long startOfRangeToLong = ipAddressToLong(startOfRange);
+        final long endOfRangeToLong = ipAddressToLong(endOfRange);
+        
+        long diff = ipAddressToLong - startOfRangeToLong;
+        
+        return (diff >= 0 && (diff <= (endOfRangeToLong - startOfRangeToLong)));
+    }
+    
+    /** 
+     * check if the java application is running on a local machine
+     * 
+     * @return true if the application is running on a local machine else false
+     **/
+    public static boolean applicationIsRunningOnLocalMachine() {
+        boolean isRunningOnLocalMachine = false;
+        
+        try {
+            final InetAddress localHost = InetAddress.getLocalHost();
+            
+            if (localHost != null) {
+                final String hostAddress = localHost.getHostAddress();
+                final String startOfIpAddressRange = "127.0.0.0";
+                final String endOfIpAddressRange = "127.255.255.255";
+                
+                if (StringUtils.isNotEmpty(hostAddress)) {
+                    isRunningOnLocalMachine = ipAddressIsInRange(hostAddress, startOfIpAddressRange, endOfIpAddressRange);
+                }
+            }
+        }
+        
+        catch (Exception exception) { }
+                
+        return isRunningOnLocalMachine;
+    }
+    
+    /** 
+     * check if the java application is not running on a local machine
+     * 
+     * @return true if the application is not running on a local machine else false
+     **/
+    public static boolean applicationIsNotRunningOnLocalMachine() {
+        return !applicationIsRunningOnLocalMachine();
+    }
+}

http://git-wip-us.apache.org/repos/asf/fineract/blob/c338c175/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/domain/Staff.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/domain/Staff.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/domain/Staff.java
index 64f6f90..175fcb7 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/domain/Staff.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/domain/Staff.java
@@ -61,6 +61,9 @@ public class Staff extends AbstractPersistableCustom<Long> {
     @Column(name = "external_id", length = 100, nullable = true, unique = true)
     private String externalId;
 
+	@Column(name = "email_address", length = 50, unique = true)
+    private String emailAddress;
+
     @ManyToOne
     @JoinColumn(name = "office_id", nullable = false)
     private Office office;
@@ -246,6 +249,10 @@ public class Staff extends AbstractPersistableCustom<Long> {
         return getId().equals(staff.getId());
     }
 
+	public String emailAddress() {
+        return emailAddress;
+    }
+
     public Long officeId() {
         return this.office.getId();
     }

http://git-wip-us.apache.org/repos/asf/fineract/blob/c338c175/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientApiConstants.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientApiConstants.java
index 090cc0a..28ed583 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientApiConstants.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientApiConstants.java
@@ -83,6 +83,7 @@ public class ClientApiConstants {
     public static final String accountNoParamName = "accountNo";
     public static final String externalIdParamName = "externalId";
     public static final String mobileNoParamName = "mobileNo";
+	public static final String emailAddressParamName = "emailAddress";
     public static final String firstnameParamName = "firstname";
     public static final String middlenameParamName = "middlename";
     public static final String lastnameParamName = "lastname";
@@ -194,7 +195,7 @@ public class ClientApiConstants {
     protected static final Set<String> CLIENT_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(idParamName,
             accountNoParamName,
             externalIdParamName, statusParamName, activeParamName, activationDateParamName, firstnameParamName, middlenameParamName,
-            lastnameParamName, fullnameParamName, displayNameParamName, mobileNoParamName, officeIdParamName, officeNameParamName,
+            lastnameParamName, fullnameParamName, displayNameParamName, mobileNoParamName, emailAddressParamName, officeIdParamName, officeNameParamName,
             transferToOfficeIdParamName, transferToOfficeNameParamName, hierarchyParamName, imageIdParamName, imagePresentParamName,
             staffIdParamName, staffNameParamName, timelineParamName, groupsParamName, officeOptionsParamName, staffOptionsParamName,
             dateOfBirthParamName, genderParamName, clientTypeParamName, clientClassificationParamName, legalFormParamName, 

http://git-wip-us.apache.org/repos/asf/fineract/blob/c338c175/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientApiCollectionConstants.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientApiCollectionConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientApiCollectionConstants.java
index 04b5fe4..acf0dc7 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientApiCollectionConstants.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientApiCollectionConstants.java
@@ -29,7 +29,7 @@ public class ClientApiCollectionConstants extends ClientApiConstants{
 
     protected static final Set<String> CLIENT_CREATE_REQUEST_DATA_PARAMETERS = new HashSet<>(
             Arrays.asList(familyMembers,address,localeParamName, dateFormatParamName, groupIdParamName, accountNoParamName, externalIdParamName,
-                    mobileNoParamName, firstnameParamName, middlenameParamName, lastnameParamName, fullnameParamName, officeIdParamName,
+                    mobileNoParamName, emailAddressParamName, firstnameParamName, middlenameParamName, lastnameParamName, fullnameParamName, officeIdParamName,
                     activeParamName, activationDateParamName, staffIdParamName, submittedOnDateParamName, savingsProductIdParamName,
                     dateOfBirthParamName, genderIdParamName, clientTypeIdParamName, clientClassificationIdParamName,
                     clientNonPersonDetailsParamName, displaynameParamName, legalFormIdParamName, datatables, isStaffParamName));
@@ -39,7 +39,7 @@ public class ClientApiCollectionConstants extends ClientApiConstants{
                     constitutionIdParamName, mainBusinessLineIdParamName, datatables));
 
     protected static final Set<String> CLIENT_UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
-            dateFormatParamName, accountNoParamName, externalIdParamName, mobileNoParamName, firstnameParamName, middlenameParamName,
+            dateFormatParamName, accountNoParamName, externalIdParamName, mobileNoParamName, emailAddressParamName, firstnameParamName, middlenameParamName,
 
             lastnameParamName, fullnameParamName, activeParamName, activationDateParamName, staffIdParamName, savingsProductIdParamName,
             dateOfBirthParamName, genderIdParamName, clientTypeIdParamName, clientClassificationIdParamName, submittedOnDateParamName, clientNonPersonDetailsParamName, displaynameParamName, legalFormIdParamName, isStaffParamName));

http://git-wip-us.apache.org/repos/asf/fineract/blob/c338c175/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientData.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientData.java
index 3827307..557725d 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientData.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientData.java
@@ -58,6 +58,7 @@ final public class ClientData implements Comparable<ClientData> {
     private final String fullname;
     private final String displayName;
     private final String mobileNo;
+	private final String emailAddress;
     private final LocalDate dateOfBirth;
     private final CodeValueData gender;
     private final CodeValueData clientType;
@@ -126,6 +127,7 @@ final public class ClientData implements Comparable<ClientData> {
         final String displayName = null;
         final String externalId = null;
         final String mobileNo = null;
+		final String emailAddress = null;
         final LocalDate dateOfBirth = null;
         final CodeValueData gender = null;
         final Long imageId = null;
@@ -143,7 +145,7 @@ final public class ClientData implements Comparable<ClientData> {
 		final Boolean isStaff = false;
         final ClientNonPersonData clientNonPersonDetails = null;
         return new ClientData(accountNo, status, subStatus, officeId, officeName, transferToOfficeId, transferToOfficeName, id, firstname,
-                middlename, lastname, fullname, displayName, externalId, mobileNo, dateOfBirth, gender, joinedDate, imageId, staffId,
+                middlename, lastname, fullname, displayName, externalId, mobileNo, emailAddress, dateOfBirth, gender, joinedDate, imageId, staffId,
                 staffName, officeOptions, groups, staffOptions, narrations, genderOptions, timeline, savingProductOptions,
                 savingsProductId, savingsProductName, savingsAccountId, savingAccountOptions, clientType, clientClassification,
                 clientTypeOptions, clientClassificationOptions, clientNonPersonConstitutionOptions, clientNonPersonMainBusinessLineOptions, 
@@ -155,7 +157,7 @@ final public class ClientData implements Comparable<ClientData> {
 
         return new ClientData(clientData.accountNo, clientData.status, clientData.subStatus, clientData.officeId, clientData.officeName,
                 clientData.transferToOfficeId, clientData.transferToOfficeName, clientData.id, clientData.firstname, clientData.middlename,
-                clientData.lastname, clientData.fullname, clientData.displayName, clientData.externalId, clientData.mobileNo,
+                clientData.lastname, clientData.fullname, clientData.displayName, clientData.externalId, clientData.mobileNo, clientData.emailAddress,
                 clientData.dateOfBirth, clientData.gender, clientData.activationDate, clientData.imageId, clientData.staffId,
                 clientData.staffName, templateData.officeOptions, clientData.groups, templateData.staffOptions, templateData.narrations,
                 templateData.genderOptions, clientData.timeline, templateData.savingProductOptions, clientData.savingsProductId,
@@ -171,7 +173,7 @@ final public class ClientData implements Comparable<ClientData> {
 
         return new ClientData(clientData.accountNo, clientData.status, clientData.subStatus, clientData.officeId, clientData.officeName,
                 clientData.transferToOfficeId, clientData.transferToOfficeName, clientData.id, clientData.firstname, clientData.middlename,
-                clientData.lastname, clientData.fullname, clientData.displayName, clientData.externalId, clientData.mobileNo,
+                clientData.lastname, clientData.fullname, clientData.displayName, clientData.externalId, clientData.mobileNo, clientData.emailAddress,
                 clientData.dateOfBirth, clientData.gender, clientData.activationDate, clientData.imageId, clientData.staffId,
                 clientData.staffName, clientData.officeOptions, clientData.groups, clientData.staffOptions, clientData.narrations,
                 clientData.genderOptions, clientData.timeline, clientData.savingProductOptions, clientData.savingsProductId,
@@ -185,7 +187,7 @@ final public class ClientData implements Comparable<ClientData> {
     public static ClientData setParentGroups(final ClientData clientData, final Collection<GroupGeneralData> parentGroups) {
         return new ClientData(clientData.accountNo, clientData.status, clientData.subStatus, clientData.officeId, clientData.officeName,
                 clientData.transferToOfficeId, clientData.transferToOfficeName, clientData.id, clientData.firstname, clientData.middlename,
-                clientData.lastname, clientData.fullname, clientData.displayName, clientData.externalId, clientData.mobileNo,
+                clientData.lastname, clientData.fullname, clientData.displayName, clientData.externalId, clientData.mobileNo, clientData.emailAddress,
                 clientData.dateOfBirth, clientData.gender, clientData.activationDate, clientData.imageId, clientData.staffId,
                 clientData.staffName, clientData.officeOptions, parentGroups, clientData.staffOptions, null, null, clientData.timeline,
                 clientData.savingProductOptions, clientData.savingsProductId, clientData.savingsProductName, clientData.savingsAccountId,
@@ -203,6 +205,7 @@ final public class ClientData implements Comparable<ClientData> {
         final String transferToOfficeName = null;
         final String externalId = null;
         final String mobileNo = null;
+		final String emailAddress = null;
         final LocalDate dateOfBirth = null;
         final CodeValueData gender = null;
         final LocalDate activationDate = null;
@@ -234,7 +237,7 @@ final public class ClientData implements Comparable<ClientData> {
 		final Boolean isStaff = false;
         final ClientNonPersonData clientNonPerson = null;
         return new ClientData(accountNo, status, subStatus, officeId, officeName, transferToOfficeId, transferToOfficeName, id, firstname,
-                middlename, lastname, fullname, displayName, externalId, mobileNo, dateOfBirth, gender, activationDate, imageId, staffId,
+                middlename, lastname, fullname, displayName, externalId, mobileNo, emailAddress, dateOfBirth, gender, activationDate, imageId, staffId,
                 staffName, allowedOffices, groups, staffOptions, closureReasons, genderOptions, timeline, savingProductOptions,
                 savingsProductId, savingsProductName, savingsAccountId, savingAccountOptions, clientType, clientClassification,
                 clientTypeOptions, clientClassificationOptions, clientNonPersonConstitutionOptions, clientNonPersonMainBusinessLineOptions, 
@@ -253,6 +256,7 @@ final public class ClientData implements Comparable<ClientData> {
         final String fullname = null;
         final String externalId = null;
         final String mobileNo = null;
+		final String emailAddress = null;
         final LocalDate dateOfBirth = null;
         final CodeValueData gender = null;
         final LocalDate activationDate = null;
@@ -282,7 +286,7 @@ final public class ClientData implements Comparable<ClientData> {
 		final Boolean isStaff = false;
         final ClientNonPersonData clientNonPerson = null;
         return new ClientData(accountNo, status, subStatus, officeId, officeName, transferToOfficeId, transferToOfficeName, id, firstname,
-                middlename, lastname, fullname, displayName, externalId, mobileNo, dateOfBirth, gender, activationDate, imageId, staffId,
+                middlename, lastname, fullname, displayName, externalId, mobileNo, emailAddress, dateOfBirth, gender, activationDate, imageId, staffId,
                 staffName, allowedOffices, groups, staffOptions, closureReasons, genderOptions, timeline, savingProductOptions,
                 savingsProductId, savingsProductName, savingsAccountId, savingAccountOptions, clientType, clientClassification,
                 clientTypeOptions, clientClassificationOptions, clientNonPersonConstitutionOptions, clientNonPersonMainBusinessLineOptions, 
@@ -293,7 +297,7 @@ final public class ClientData implements Comparable<ClientData> {
     public static ClientData instance(final String accountNo, final EnumOptionData status, final CodeValueData subStatus,
             final Long officeId, final String officeName, final Long transferToOfficeId, final String transferToOfficeName, final Long id,
             final String firstname, final String middlename, final String lastname, final String fullname, final String displayName,
-            final String externalId, final String mobileNo, final LocalDate dateOfBirth, final CodeValueData gender,
+            final String externalId, final String mobileNo, final String emailAddress, final LocalDate dateOfBirth, final CodeValueData gender,
             final LocalDate activationDate, final Long imageId, final Long staffId, final String staffName,
             final ClientTimelineData timeline, final Long savingsProductId, final String savingsProductName, final Long savingsAccountId,
             final CodeValueData clientType, final CodeValueData clientClassification, final EnumOptionData legalForm, final ClientNonPersonData clientNonPerson, final Boolean isStaff) {
@@ -311,7 +315,7 @@ final public class ClientData implements Comparable<ClientData> {
         final List<EnumOptionData> clientLegalFormOptions = null;
         final ClientFamilyMembersData familyMemberOptions=null;
         return new ClientData(accountNo, status, subStatus, officeId, officeName, transferToOfficeId, transferToOfficeName, id, firstname,
-                middlename, lastname, fullname, displayName, externalId, mobileNo, dateOfBirth, gender, activationDate, imageId, staffId,
+                middlename, lastname, fullname, displayName, externalId, mobileNo, emailAddress, dateOfBirth, gender, activationDate, imageId, staffId,
                 staffName, allowedOffices, groups, staffOptions, closureReasons, genderOptions, timeline, savingProductOptions,
                 savingsProductId, savingsProductName, savingsAccountId, null, clientType, clientClassification, clientTypeOptions,
                 clientClassificationOptions, clientNonPersonConstitutionOptions, clientNonPersonMainBusinessLineOptions, clientNonPerson,
@@ -322,7 +326,7 @@ final public class ClientData implements Comparable<ClientData> {
     private ClientData(final String accountNo, final EnumOptionData status, final CodeValueData subStatus, final Long officeId,
             final String officeName, final Long transferToOfficeId, final String transferToOfficeName, final Long id,
             final String firstname, final String middlename, final String lastname, final String fullname, final String displayName,
-            final String externalId, final String mobileNo, final LocalDate dateOfBirth, final CodeValueData gender,
+            final String externalId, final String mobileNo, final String emailAddress, final LocalDate dateOfBirth, final CodeValueData gender,
             final LocalDate activationDate, final Long imageId, final Long staffId, final String staffName,
             final Collection<OfficeData> allowedOffices, final Collection<GroupGeneralData> groups,
             final Collection<StaffData> staffOptions, final Collection<CodeValueData> narrations,
@@ -354,6 +358,7 @@ final public class ClientData implements Comparable<ClientData> {
         this.displayName = StringUtils.defaultIfEmpty(displayName, null);
         this.externalId = StringUtils.defaultIfEmpty(externalId, null);
         this.mobileNo = StringUtils.defaultIfEmpty(mobileNo, null);
+		this.emailAddress = StringUtils.defaultIfEmpty(emailAddress, null);
         this.activationDate = activationDate;
         this.dateOfBirth = dateOfBirth;
         this.gender = gender;
@@ -440,6 +445,7 @@ final public class ClientData implements Comparable<ClientData> {
                 .append(this.id, obj.id) //
                 .append(this.displayName, obj.displayName) //
                 .append(this.mobileNo, obj.mobileNo) //
+				.append(this.emailAddress, obj.emailAddress) //
                 .toComparison();
     }
 
@@ -453,6 +459,7 @@ final public class ClientData implements Comparable<ClientData> {
                 .append(this.id, rhs.id) //
                 .append(this.displayName, rhs.displayName) //
                 .append(this.mobileNo, rhs.mobileNo) //
+				.append(this.emailAddress, rhs.emailAddress) //
                 .isEquals();
     }
 

http://git-wip-us.apache.org/repos/asf/fineract/blob/c338c175/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/Client.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/Client.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/Client.java
index 08425f7..1b1b393 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/Client.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/Client.java
@@ -114,6 +114,9 @@ public final class Client extends AbstractPersistableCustom<Long> {
 
     @Column(name = "mobile_no", length = 50, nullable = false, unique = true)
     private String mobileNo;
+	
+	@Column(name = "email_address", length = 50, unique = true)
+    private String emailAddress;
 
 	@Column(name = "is_staff", nullable = false)
     private boolean isStaff;
@@ -236,6 +239,7 @@ public final class Client extends AbstractPersistableCustom<Long> {
         final String accountNo = command.stringValueOfParameterNamed(ClientApiConstants.accountNoParamName);
         final String externalId = command.stringValueOfParameterNamed(ClientApiConstants.externalIdParamName);
         final String mobileNo = command.stringValueOfParameterNamed(ClientApiConstants.mobileNoParamName);
+		final String emailAddress = command.stringValueOfParameterNamed(ClientApiConstants.emailAddressParamName);
 
         final String firstname = command.stringValueOfParameterNamed(ClientApiConstants.firstnameParamName);
         final String middlename = command.stringValueOfParameterNamed(ClientApiConstants.middlenameParamName);
@@ -269,7 +273,7 @@ public final class Client extends AbstractPersistableCustom<Long> {
         }
         final Long savingsAccountId = null;
         return new Client(currentUser, status, clientOffice, clientParentGroup, accountNo, firstname, middlename, lastname, fullname,
-                activationDate, officeJoiningDate, externalId, mobileNo, staff, submittedOnDate, savingsProductId, savingsAccountId, dataOfBirth,
+                activationDate, officeJoiningDate, externalId, mobileNo, emailAddress, staff, submittedOnDate, savingsProductId, savingsAccountId, dataOfBirth,
                 gender, clientType, clientClassification, legalForm, isStaff);
     }
 
@@ -279,7 +283,7 @@ public final class Client extends AbstractPersistableCustom<Long> {
 
     private Client(final AppUser currentUser, final ClientStatus status, final Office office, final Group clientParentGroup,
             final String accountNo, final String firstname, final String middlename, final String lastname, final String fullname,
-            final LocalDate activationDate, final LocalDate officeJoiningDate, final String externalId, final String mobileNo,
+            final LocalDate activationDate, final LocalDate officeJoiningDate, final String externalId, final String mobileNo, final String emailAddress,
             final Staff staff, final LocalDate submittedOnDate, final Long savingsProductId, final Long savingsAccountId,
             final LocalDate dateOfBirth, final CodeValue gender, final CodeValue clientType, final CodeValue clientClassification, final Integer legalForm, final Boolean isStaff) {
 
@@ -307,6 +311,12 @@ public final class Client extends AbstractPersistableCustom<Long> {
             this.mobileNo = null;
         }
 
+		if (StringUtils.isNotBlank(emailAddress)) {
+            this.emailAddress = emailAddress.trim();
+        } else {
+            this.emailAddress = null;
+        }
+
         if (activationDate != null) {
             this.activationDate = activationDate.toDateTimeAtStartOfDay().toDate();
             this.activatedBy = currentUser;
@@ -498,6 +508,12 @@ public final class Client extends AbstractPersistableCustom<Long> {
             actualChanges.put(ClientApiConstants.mobileNoParamName, newValue);
             this.mobileNo = StringUtils.defaultIfEmpty(newValue, null);
         }
+		
+		if (command.isChangeInStringParameterNamed(ClientApiConstants.emailAddressParamName, this.emailAddress)) {
+            final String newValue = command.stringValueOfParameterNamed(ClientApiConstants.emailAddressParamName);
+            actualChanges.put(ClientApiConstants.emailAddressParamName, newValue);
+            this.emailAddress = StringUtils.defaultIfEmpty(newValue, null);
+        }
 
         if (command.isChangeInStringParameterNamed(ClientApiConstants.firstnameParamName, this.firstname)) {
             final String newValue = command.stringValueOfParameterNamed(ClientApiConstants.firstnameParamName);
@@ -771,6 +787,10 @@ public final class Client extends AbstractPersistableCustom<Long> {
         return this.mobileNo;
     }
 
+	public String emailAddress() {
+        return this.emailAddress;
+    }
+
     public void setMobileNo(final String mobileNo) {
         this.mobileNo = mobileNo;
     }
@@ -787,6 +807,10 @@ public final class Client extends AbstractPersistableCustom<Long> {
 		return this.externalId; 
 	}
 
+	public void setEmailAddress(final String emailAddress) {
+        this.emailAddress = emailAddress;
+    }
+
     public String getDisplayName() {
         return this.displayName;
     }

http://git-wip-us.apache.org/repos/asf/fineract/blob/c338c175/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformServiceImpl.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformServiceImpl.java
index b5e8825..4d54c1e 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformServiceImpl.java
@@ -363,6 +363,7 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService
             sqlBuilder.append("c.fullname as fullname, c.display_name as displayName, ");
             sqlBuilder.append("c.mobile_no as mobileNo, ");
 			sqlBuilder.append("c.is_staff as isStaff, ");
+			sqlBuilder.append("c.email_address as emailAddress, ");
             sqlBuilder.append("c.date_of_birth as dateOfBirth, ");
             sqlBuilder.append("c.gender_cv_id as genderId, ");
             sqlBuilder.append("cv.code_value as genderValue, ");
@@ -452,6 +453,7 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService
             final String externalId = rs.getString("externalId");
             final String mobileNo = rs.getString("mobileNo");
 			final boolean isStaff = rs.getBoolean("isStaff");
+			final String emailAddress = rs.getString("emailAddress");
             final LocalDate dateOfBirth = JdbcSupport.getLocalDate(rs, "dateOfBirth");
             final Long genderId = JdbcSupport.getLong(rs, "genderId");
             final String genderValue = rs.getString("genderValue");
@@ -511,7 +513,7 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService
                     closedByUsername, closedByFirstname, closedByLastname);
 
             return ClientData.instance(accountNo, status, subStatus, officeId, officeName, transferToOfficeId, transferToOfficeName, id,
-                    firstname, middlename, lastname, fullname, displayName, externalId, mobileNo, dateOfBirth, gender, activationDate,
+                    firstname, middlename, lastname, fullname, displayName, externalId, mobileNo, emailAddress, dateOfBirth, gender, activationDate,
                     imageId, staffId, staffName, timeline, savingsProductId, savingsProductName, savingsAccountId, clienttype,
                     classification, legalForm, clientNonPerson, isStaff);
 
@@ -547,6 +549,7 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService
             builder.append("c.fullname as fullname, c.display_name as displayName, ");
             builder.append("c.mobile_no as mobileNo, ");
 			builder.append("c.is_staff as isStaff, ");
+			builder.append("c.email_address as emailAddress, ");
             builder.append("c.date_of_birth as dateOfBirth, ");
             builder.append("c.gender_cv_id as genderId, ");
             builder.append("cv.code_value as genderValue, ");
@@ -635,6 +638,7 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService
             final String externalId = rs.getString("externalId");
             final String mobileNo = rs.getString("mobileNo");
 			final boolean isStaff = rs.getBoolean("isStaff");
+			final String emailAddress = rs.getString("emailAddress");
             final LocalDate dateOfBirth = JdbcSupport.getLocalDate(rs, "dateOfBirth");
             final Long genderId = JdbcSupport.getLong(rs, "genderId");
             final String genderValue = rs.getString("genderValue");
@@ -693,7 +697,7 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService
                     closedByUsername, closedByFirstname, closedByLastname);
 
             return ClientData.instance(accountNo, status, subStatus, officeId, officeName, transferToOfficeId, transferToOfficeName, id,
-                    firstname, middlename, lastname, fullname, displayName, externalId, mobileNo, dateOfBirth, gender, activationDate,
+                    firstname, middlename, lastname, fullname, displayName, externalId, mobileNo, emailAddress, dateOfBirth, gender, activationDate,
                     imageId, staffId, staffName, timeline, savingsProductId, savingsProductName, savingsAccountId, clienttype,
                     classification, legalForm, clientNonPerson, isStaff);
 

http://git-wip-us.apache.org/repos/asf/fineract/blob/c338c175/fineract-provider/src/main/resources/sql/migrations/core_db/V322_1__scheduled_email_campaign.sql
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V322_1__scheduled_email_campaign.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V322_1__scheduled_email_campaign.sql
new file mode 100644
index 0000000..f581a0c
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V322_1__scheduled_email_campaign.sql
@@ -0,0 +1,169 @@
+--
+-- 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.
+--
+create table if not exists scheduled_email_campaign (
+id bigint(20) NOT NULL AUTO_INCREMENT,
+campaign_name varchar(100) NOT NULL,
+campaign_type int NOT NULL,
+businessRule_id int NOT NULL,
+param_value text,
+status_enum int NOT NULL,
+closedon_date date,
+closedon_userid bigint(20),
+submittedon_date date,
+submittedon_userid bigint(20),
+approvedon_date date,
+approvedon_userid bigint(20),
+recurrence varchar(100),
+next_trigger_date datetime,
+last_trigger_date datetime,
+recurrence_start_date datetime,
+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_status varchar(10) null,
+previous_run_error_log text null,
+previous_run_error_message text null,
+is_visible tinyint(1) null,
+foreign key (submittedon_userid) references m_appuser(id),
+foreign key (stretchy_report_id) references stretchy_report(id),
+  PRIMARY KEY (id)
+)ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE IF NOT EXISTS scheduled_email_messages_outbound (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `group_id` bigint(20) DEFAULT NULL,
+  `client_id` bigint(20) DEFAULT NULL,
+  `staff_id` bigint(20) DEFAULT NULL,
+  `email_campaign_id` bigint(20) DEFAULT NULL,
+  `status_enum` int(5) NOT NULL DEFAULT '100',
+  `email_address` varchar(50) NOT NULL,
+  `email_subject` varchar(50) NOT NULL,
+  `message` text NOT NULL,
+  `campaign_name` varchar(200) DEFAULT NULL,
+  `submittedon_date` date,
+  `error_message` text,
+  PRIMARY KEY (`id`),
+  KEY `SEFKGROUP000000001` (`group_id`),
+  KEY `SEFKCLIENT00000001` (`client_id`),
+  key `SEFKSTAFF000000001` (`staff_id`),
+  CONSTRAINT `SEFKGROUP000000001` FOREIGN KEY (`group_id`) REFERENCES `m_group` (`id`),
+  CONSTRAINT `SEFKCLIENT00000001` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`),
+  CONSTRAINT `SEFKSTAFF000000001` FOREIGN KEY (`staff_id`) REFERENCES `m_staff` (`id`),
+  CONSTRAINT `fk_schedule_email_campign` FOREIGN KEY (`email_campaign_id`) REFERENCES `scheduled_email_campaign` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+create table if not exists scheduled_email_configuration (
+id int primary key auto_increment,
+name varchar(50) not null,
+`value` varchar(200) null,
+constraint unique_name unique (name)
+);
+
+DELETE FROM `m_permission` WHERE `code`='READ_EMAIL';
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('organisation', 'READ_EMAIL', 'EMAIL', 'READ', 0);
+
+DELETE FROM `m_permission` WHERE `code`='CREATE_EMAIL';
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('organisation', 'CREATE_EMAIL', 'EMAIL', 'CREATE', 0);
+
+DELETE FROM `m_permission` WHERE `code`='CREATE_EMAIL_CHECKER';
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('organisation', 'CREATE_EMAIL_CHECKER', 'EMAIL', 'CREATE_CHECKER', 0);
+
+DELETE FROM `m_permission` WHERE `code`='UPDATE_EMAIL';
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('organisation', 'UPDATE_EMAIL', 'EMAIL', 'UPDATE', 0);
+
+DELETE FROM `m_permission` WHERE `code`='UPDATE_EMAIL_CHECKER';
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('organisation', 'UPDATE_EMAIL_CHECKER', 'EMAIL', 'UPDATE_CHECKER', 0);
+
+DELETE FROM `m_permission` WHERE `code`='DELETE_EMAIL';
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('organisation', 'DELETE_EMAIL', 'EMAIL', 'DELETE', 0);
+
+DELETE FROM `m_permission` WHERE `code`='DELETE_EMAIL_CHECKER';
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('organisation', 'DELETE_EMAIL_CHECKER', 'EMAIL', 'DELETE_CHECKER', 0);
+
+DELETE FROM `m_permission` WHERE `code`='READ_EMAIL_CAMPAIGN';
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('organisation', 'READ_EMAIL_CAMPAIGN', 'EMAIL_CAMPAIGN', 'READ', 0);
+
+DELETE FROM `m_permission` WHERE `code`='CREATE_EMAIL_CAMPAIGN';
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('organisation', 'CREATE_EMAIL_CAMPAIGN', 'EMAIL_CAMPAIGN', 'CREATE', 0);
+
+DELETE FROM `m_permission` WHERE `code`='CREATE_EMAIL_CAMPAIGN_CHECKER';
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('organisation', 'CREATE_EMAIL_CAMPAIGN_CHECKER', 'EMAIL_CAMPAIGN', 'CREATE_CHECKER', 0);
+
+DELETE FROM `m_permission` WHERE `code`='UPDATE_EMAIL_CAMPAIGN';
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('organisation', 'UPDATE_EMAIL_CAMPAIGN', 'EMAIL_CAMPAIGN', 'UPDATE', 0);
+
+DELETE FROM `m_permission` WHERE `code`='UPDATE_EMAIL_CAMPAIGN_CHECKER';
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('organisation', 'UPDATE_EMAIL_CAMPAIGN_CHECKER', 'EMAIL_CAMPAIGN', 'UPDATE_CHECKER', 0);
+
+DELETE FROM `m_permission` WHERE `code`='DELETE_EMAIL_CAMPAIGN';
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('organisation', 'DELETE_EMAIL_CAMPAIGN', 'EMAIL_CAMPAIGN', 'DELETE', 0);
+
+DELETE FROM `m_permission` WHERE `code`='DELETE_EMAIL_CAMPAIGN_CHECKER';
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('organisation', 'DELETE_EMAIL_CAMPAIGN_CHECKER', 'EMAIL_CAMPAIGN', 'DELETE_CHECKER', 0);
+
+DELETE FROM `m_permission` WHERE `code`='CLOSE_EMAIL_CAMPAIGN';
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('organisation', 'CLOSE_EMAIL_CAMPAIGN', 'EMAIL_CAMPAIGN', 'CLOSE', 0);
+
+DELETE FROM `m_permission` WHERE `code`='ACTIVATE_EMAIL_CAMPAIGN';
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('organisation', 'ACTIVATE_EMAIL_CAMPAIGN', 'EMAIL_CAMPAIGN', 'ACTIVATE', 0);
+
+DELETE FROM `m_permission` WHERE `code`='REACTIVATE_EMAIL_CAMPAIGN';
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('organisation', 'REACTIVATE_EMAIL_CAMPAIGN', 'EMAIL_CAMPAIGN', 'REACTIVATE', 0);
+
+
+INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
+VALUES ('organisation', 'READ_EMAIL_CONFIGURATION', 'EMAIL_CONFIGURATION', 'READ', 0),
+('organisation', 'UPDATE_EMAIL_CONFIGURATION', 'EMAIL_CONFIGURATION', 'UPDATE', 0);
+
+Alter table m_client
+ADD Column email_address varchar(150);
+
+Alter table m_staff
+ADD Column email_address varchar(150);
+
+
+insert into job (name, display_name, cron_expression, create_time)
+values ('Execute Email', 'Execute Email', '0 0/10 * * * ?', NOW());
+
+insert into job (name, display_name, cron_expression, create_time)
+values ('Update Email Outbound with campaign message', 'Update Email Outbound with campaign message', '0 0/15 * * * ?', NOW());
+
+INSERT INTO `scheduled_email_configuration` (`name`)
+VALUES ('SMTP_SERVER'),
+('SMTP_PORT'),('SMTP_USERNAME'), ('SMTP_PASSWORD');
+


Mime
View raw message