You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by ra...@apache.org on 2016/11/14 14:06:08 UTC

[06/14] incubator-fineract git commit: SMS Campaign feature implementation

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/911cab85/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageAssembler.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageAssembler.java
index 32a56e1..695edfb 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageAssembler.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageAssembler.java
@@ -18,6 +18,9 @@
  */
 package org.apache.fineract.infrastructure.sms.domain;
 
+import org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaign;
+import org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaignRepository;
+import org.apache.fineract.infrastructure.campaigns.sms.exception.SmsCampaignNotFound;
 import org.apache.fineract.infrastructure.core.api.JsonCommand;
 import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
 import org.apache.fineract.infrastructure.sms.SmsApiConstants;
@@ -40,17 +43,19 @@ public class SmsMessageAssembler {
     private final GroupRepositoryWrapper groupRepository;
     private final ClientRepositoryWrapper clientRepository;
     private final StaffRepositoryWrapper staffRepository;
+    private final SmsCampaignRepository smsCampaignRepository;
     private final FromJsonHelper fromApiJsonHelper;
 
     @Autowired
     public SmsMessageAssembler(final SmsMessageRepository smsMessageRepository, final GroupRepositoryWrapper groupRepositoryWrapper,
             final ClientRepositoryWrapper clientRepository, final StaffRepositoryWrapper staffRepository,
-            final FromJsonHelper fromApiJsonHelper) {
+            final FromJsonHelper fromApiJsonHelper, final SmsCampaignRepository smsCampaignRepository) {
         this.smsMessageRepository = smsMessageRepository;
         this.groupRepository = groupRepositoryWrapper;
         this.clientRepository = clientRepository;
         this.staffRepository = staffRepository;
         this.fromApiJsonHelper = fromApiJsonHelper;
+        this.smsCampaignRepository = smsCampaignRepository;
     }
 
     public SmsMessage assembleFromJson(final JsonCommand command) {
@@ -58,13 +63,20 @@ public class SmsMessageAssembler {
         final JsonElement element = command.parsedJson();
 
         String mobileNo = null;
-
         Group group = null;
+        Long externalId = null;
         if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.groupIdParamName, element)) {
             final Long groupId = this.fromApiJsonHelper.extractLongNamed(SmsApiConstants.groupIdParamName, element);
             group = this.groupRepository.findOneWithNotFoundDetection(groupId);
         }
 
+        SmsCampaign smsCampaign = null;
+        if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.campaignIdParamName, element)) {
+            final Long campaignId = this.fromApiJsonHelper.extractLongNamed(SmsApiConstants.campaignIdParamName, element);
+            smsCampaign = this.smsCampaignRepository.findOne(campaignId);
+            if (smsCampaign == null) { throw new SmsCampaignNotFound(campaignId); }
+        }
+
         Client client = null;
         if (this.fromApiJsonHelper.parameterExists(SmsApiConstants.clientIdParamName, element)) {
             final Long clientId = this.fromApiJsonHelper.extractLongNamed(SmsApiConstants.clientIdParamName, element);
@@ -81,7 +93,7 @@ public class SmsMessageAssembler {
 
         final String message = this.fromApiJsonHelper.extractStringNamed(SmsApiConstants.messageParamName, element);
 
-        return SmsMessage.pendingSms(group, client, staff, message, mobileNo);
+        return SmsMessage.pendingSms(externalId, group, client, staff, message, mobileNo, smsCampaign);
     }
 
     public SmsMessage assembleFromResourceId(final Long resourceId) {

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/911cab85/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageRepository.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageRepository.java
index 4d9768a..9dadb59 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageRepository.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageRepository.java
@@ -18,9 +18,12 @@
  */
 package org.apache.fineract.infrastructure.sms.domain;
 
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
 
 public interface SmsMessageRepository extends JpaRepository<SmsMessage, Long>, JpaSpecificationExecutor<SmsMessage> {
-    // no extra behaviour
+
+    Page<SmsMessage> findByStatusType(final Integer status, Pageable pageable);
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/911cab85/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageStatusType.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageStatusType.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageStatusType.java
index 72dce38..a97c2c6 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageStatusType.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/domain/SmsMessageStatusType.java
@@ -22,6 +22,7 @@ public enum SmsMessageStatusType {
 
     INVALID(0, "smsMessageStatusType.invalid"), //
     PENDING(100, "smsMessageStatusType.pending"), //
+    WAITING_FOR_DELIVERY_REPORT(150, "smsMessageStatusType.waitingForDeliveryReport"), 
     SENT(200, "smsMessageStatusType.sent"), //
     DELIVERED(300, "smsMessageStatusType.delivered"), //
     FAILED(400, "smsMessageStatusType.failed");
@@ -36,6 +37,9 @@ public enum SmsMessageStatusType {
             case 100:
                 enumeration = SmsMessageStatusType.PENDING;
             break;
+            case 150:
+                enumeration = SmsMessageStatusType.WAITING_FOR_DELIVERY_REPORT;
+            break;
             case 200:
                 enumeration = SmsMessageStatusType.SENT;
             break;

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/911cab85/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/exception/SmsCountryCodeNotFoundException.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/exception/SmsCountryCodeNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/exception/SmsCountryCodeNotFoundException.java
new file mode 100644
index 0000000..bfdbaad
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/exception/SmsCountryCodeNotFoundException.java
@@ -0,0 +1,28 @@
+/**
+ * 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.sms.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+public class SmsCountryCodeNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public SmsCountryCodeNotFoundException() {
+        super("error.msg.sms.country.code.not.found", "SMS country code does not exist");
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/911cab85/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/scheduler/SmsMessageScheduledJobService.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/scheduler/SmsMessageScheduledJobService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/scheduler/SmsMessageScheduledJobService.java
new file mode 100644
index 0000000..8d3e545
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/scheduler/SmsMessageScheduledJobService.java
@@ -0,0 +1,47 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.sms.scheduler;
+
+import java.util.Collection;
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaign;
+import org.apache.fineract.infrastructure.sms.domain.SmsMessage;
+
+/**
+ * Scheduled Job service interface for SMS message
+ **/
+public interface SmsMessageScheduledJobService {
+
+    /**
+     * sends a batch of SMS messages to the SMS gateway
+     **/
+    public void sendMessagesToGateway();
+
+    /**
+     * sends triggered batch SMS messages to SMS gateway
+     * @param smsDataMap
+     */
+    public void sendTriggeredMessages(Map<SmsCampaign, Collection<SmsMessage>> smsDataMap);
+
+    /**
+     * get delivery report from the SMS gateway
+     **/
+    public void getDeliveryReports();
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/911cab85/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/scheduler/SmsMessageScheduledJobServiceImpl.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/scheduler/SmsMessageScheduledJobServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/scheduler/SmsMessageScheduledJobServiceImpl.java
new file mode 100644
index 0000000..3e23a10
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/scheduler/SmsMessageScheduledJobServiceImpl.java
@@ -0,0 +1,295 @@
+/**
+ * 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.sms.scheduler;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import javax.annotation.PostConstruct;
+
+import org.apache.fineract.infrastructure.campaigns.helper.SmsConfigUtils;
+import org.apache.fineract.infrastructure.campaigns.sms.constants.SmsCampaignConstants;
+import org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaign;
+import org.apache.fineract.infrastructure.campaigns.sms.exception.ConnectionFailureException;
+import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import org.apache.fineract.infrastructure.jobs.annotation.CronTarget;
+import org.apache.fineract.infrastructure.jobs.service.JobName;
+import org.apache.fineract.infrastructure.sms.data.SmsMessageApiQueueResourceData;
+import org.apache.fineract.infrastructure.sms.data.SmsMessageDeliveryReportData;
+import org.apache.fineract.infrastructure.sms.domain.SmsMessage;
+import org.apache.fineract.infrastructure.sms.domain.SmsMessageRepository;
+import org.apache.fineract.infrastructure.sms.domain.SmsMessageStatusType;
+import org.apache.fineract.infrastructure.sms.service.SmsReadPlatformService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.event.ContextClosedEvent;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.client.RestTemplate;
+
+import com.google.gson.Gson;
+
+/**
+ * Scheduled job services that send SMS messages and get delivery reports for
+ * the sent SMS messages
+ **/
+@Service
+public class SmsMessageScheduledJobServiceImpl implements SmsMessageScheduledJobService {
+
+    private final SmsMessageRepository smsMessageRepository;
+    private final SmsReadPlatformService smsReadPlatformService;
+    private static final Logger logger = LoggerFactory.getLogger(SmsMessageScheduledJobServiceImpl.class);
+    private final RestTemplate restTemplate = new RestTemplate();
+    private  ExecutorService genericExecutorService ;
+    private ExecutorService triggeredExecutorService ;
+    private final SmsConfigUtils smsConfigUtils ;
+    
+    
+    /**
+     * SmsMessageScheduledJobServiceImpl constructor
+     **/
+    @Autowired
+    public SmsMessageScheduledJobServiceImpl(SmsMessageRepository smsMessageRepository, SmsReadPlatformService smsReadPlatformService,
+            final SmsConfigUtils smsConfigUtils) {
+        this.smsMessageRepository = smsMessageRepository;
+        this.smsReadPlatformService = smsReadPlatformService;
+        this.smsConfigUtils = smsConfigUtils ;
+    }
+
+    @PostConstruct
+    public void initializeExecutorService() {
+        genericExecutorService = Executors.newSingleThreadExecutor();
+        triggeredExecutorService = Executors.newSingleThreadExecutor() ;
+    }
+
+    /**
+     * Send batches of SMS messages to the SMS gateway (or intermediate gateway)
+     **/
+    @Override
+    @Transactional
+    @CronTarget(jobName = JobName.SEND_MESSAGES_TO_SMS_GATEWAY)
+    public void sendMessagesToGateway() {
+        Integer pageLimit = 200;
+        Integer page = 0;
+        int totalRecords = 0;
+        do {
+            PageRequest pageRequest = new PageRequest(0, pageLimit);
+            org.springframework.data.domain.Page<SmsMessage> pendingMessages = this.smsMessageRepository.findByStatusType(
+                    SmsMessageStatusType.PENDING.getValue(), pageRequest);
+            List<SmsMessage> toSaveMessages = new ArrayList<>() ;
+            try {
+
+                if (pendingMessages.getContent().size() > 0) {
+                    final String tenantIdentifier = ThreadLocalContextUtil.getTenant().getTenantIdentifier();
+                    Iterator<SmsMessage> pendingMessageIterator = pendingMessages.iterator();
+                    Collection<SmsMessageApiQueueResourceData> apiQueueResourceDatas = new ArrayList<>();
+                    while (pendingMessageIterator.hasNext()) {
+                        SmsMessage smsData = pendingMessageIterator.next();
+
+                        SmsMessageApiQueueResourceData apiQueueResourceData = SmsMessageApiQueueResourceData.instance(smsData.getId(),
+                                tenantIdentifier, null, null, smsData.getMobileNo(), smsData.getMessage(), smsData.getSmsCampaign()
+                                        .getProviderId());
+                        apiQueueResourceDatas.add(apiQueueResourceData);
+                        smsData.setStatusType(SmsMessageStatusType.WAITING_FOR_DELIVERY_REPORT.getValue());
+                        toSaveMessages.add(smsData) ;
+                    }
+                    this.smsMessageRepository.save(toSaveMessages);
+                    this.smsMessageRepository.flush();
+                    this.genericExecutorService.execute(new SmsTask(ThreadLocalContextUtil.getTenant(), apiQueueResourceDatas));
+
+//                    new MyThread(ThreadLocalContextUtil.getTenant(), apiQueueResourceDatas).start();
+                }
+            } catch (Exception e) {
+                throw new ConnectionFailureException(SmsCampaignConstants.SMS);
+            }
+            page ++;
+            totalRecords = pendingMessages.getTotalPages();
+        } while (page < totalRecords);
+    }
+
+    class SmsTask implements Runnable, ApplicationListener<ContextClosedEvent> {
+
+        private final FineractPlatformTenant tenant;
+        private final Collection<SmsMessageApiQueueResourceData> apiQueueResourceDatas;
+
+        public SmsTask(final FineractPlatformTenant tenant, final Collection<SmsMessageApiQueueResourceData> apiQueueResourceDatas) {
+            this.tenant = tenant;
+            this.apiQueueResourceDatas = apiQueueResourceDatas;
+        }
+
+        @Override
+        public void run() {
+            ThreadLocalContextUtil.setTenant(tenant);
+            connectAndSendToIntermediateServer(tenant.getTenantIdentifier(), apiQueueResourceDatas);
+        }
+
+        @Override
+        public void onApplicationEvent(ContextClosedEvent event) {
+            genericExecutorService.shutdown();
+            logger.info("Shutting down the ExecutorService");
+        }
+    }
+
+    private void connectAndSendToIntermediateServer(String tenantIdentifier,
+            Collection<SmsMessageApiQueueResourceData> apiQueueResourceDatas) {
+    	Map<String, Object> hostConfig = this.smsConfigUtils.getMessageGateWayRequestURI("sms", SmsMessageApiQueueResourceData.toJsonString(apiQueueResourceDatas)) ;
+        URI uri = (URI)hostConfig.get("uri") ;
+        HttpEntity<?> entity = (HttpEntity<?>)hostConfig.get("entity") ;
+        ResponseEntity<String> responseOne = restTemplate.exchange(uri, HttpMethod.POST, entity,
+                new ParameterizedTypeReference<String>() {});
+        if (responseOne != null) {
+//            String smsResponse = responseOne.getBody();
+            if (!responseOne.getStatusCode().equals(HttpStatus.ACCEPTED)) {
+                System.out.println(responseOne.getStatusCode().name());
+                throw new ConnectionFailureException(SmsCampaignConstants.SMS);
+            }
+        }
+    }
+
+    @Override
+    public void sendTriggeredMessages(Map<SmsCampaign, Collection<SmsMessage>> smsDataMap) {
+        try {
+            if (!smsDataMap.isEmpty()) {
+                for (Entry<SmsCampaign, Collection<SmsMessage>> entry : smsDataMap.entrySet()) {
+                    Iterator<SmsMessage> smsMessageIterator = entry.getValue().iterator();
+                    Collection<SmsMessageApiQueueResourceData> apiQueueResourceDatas = new ArrayList<>();
+                    StringBuilder request = new StringBuilder();
+                    while (smsMessageIterator.hasNext()) {
+                        SmsMessage smsMessage = smsMessageIterator.next();
+                        SmsMessageApiQueueResourceData apiQueueResourceData = SmsMessageApiQueueResourceData.instance(smsMessage.getId(),
+                                null, null, null, smsMessage.getMobileNo(), smsMessage.getMessage(), entry.getKey().getProviderId());
+                        apiQueueResourceDatas.add(apiQueueResourceData);
+                        smsMessage.setStatusType(SmsMessageStatusType.WAITING_FOR_DELIVERY_REPORT.getValue());
+                    }
+                    this.smsMessageRepository.save(entry.getValue()) ;
+                    request.append(SmsMessageApiQueueResourceData.toJsonString(apiQueueResourceDatas));
+                    logger.info("Sending triggered SMS with request - " + request.toString());
+                    this.triggeredExecutorService.execute(new SmsTask(ThreadLocalContextUtil.getTenant(), apiQueueResourceDatas));
+                }
+            }
+        } catch (Exception e) {
+            logger.error(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * get SMS message delivery reports from the SMS gateway (or intermediate
+     * gateway)
+     **/
+    @Override
+    @Transactional
+    @CronTarget(jobName = JobName.GET_DELIVERY_REPORTS_FROM_SMS_GATEWAY)
+    public void getDeliveryReports() {
+        int page = 0;
+        int totalRecords = 0;
+        Integer limit = 200;
+        do {
+            Page<Long> smsMessageInternalIds = this.smsReadPlatformService.retrieveAllWaitingForDeliveryReport(limit);
+            // only proceed if there are sms message with status type enum 300
+            try {
+
+                if (smsMessageInternalIds.getPageItems().size() > 0) {
+                    // make request
+                	Map<String, Object> hostConfig = this.smsConfigUtils.getMessageGateWayRequestURI("sms", new Gson().toJson(smsMessageInternalIds.getPageItems())) ;
+                    URI uri = (URI)hostConfig.get("uri") ;
+                    HttpEntity<?> entity = (HttpEntity<?>)hostConfig.get("entity") ;
+                    ResponseEntity<Collection<SmsMessageDeliveryReportData>> responseOne = restTemplate.exchange(uri, HttpMethod.POST, entity,
+                            new ParameterizedTypeReference<Collection<SmsMessageDeliveryReportData>>() {});
+
+                    Collection<SmsMessageDeliveryReportData> smsMessageDeliveryReportDatas = responseOne.getBody();
+                    Iterator<SmsMessageDeliveryReportData> responseReportIterator = smsMessageDeliveryReportDatas.iterator();
+                    while (responseReportIterator.hasNext()) {
+                        SmsMessageDeliveryReportData smsMessageDeliveryReportData = responseReportIterator.next();
+                        Integer deliveryStatus = smsMessageDeliveryReportData.getDeliveryStatus();
+
+                        if (!smsMessageDeliveryReportData.getHasError()
+                                && (deliveryStatus != 100)) {
+                            SmsMessage smsMessage = this.smsMessageRepository.findOne(smsMessageDeliveryReportData.getId());
+                            Integer statusType = smsMessage.getStatusType();
+                            boolean statusChanged = false;
+
+                            switch (deliveryStatus) {
+                                case 0:
+                                    statusType = SmsMessageStatusType.INVALID.getValue();
+                                break;
+                                case 150:
+                                    statusType = SmsMessageStatusType.WAITING_FOR_DELIVERY_REPORT.getValue();
+                                break;
+                                case 200:
+                                    statusType = SmsMessageStatusType.SENT.getValue();
+                                break;
+                                case 300:
+                                    statusType = SmsMessageStatusType.DELIVERED.getValue();
+                                break;
+
+                                case 400:
+                                    statusType = SmsMessageStatusType.FAILED.getValue();
+                                break;
+
+                                default:
+                                    statusType = smsMessage.getStatusType();
+                                break;
+                            }
+
+                            statusChanged = !statusType.equals(smsMessage.getStatusType());
+
+                            // update the status Type enum
+                            smsMessage.setStatusType(statusType);
+
+                            // save the SmsMessage entity
+                            this.smsMessageRepository.save(smsMessage);
+
+                            if (statusChanged) {
+                                logger.info("Status of SMS message id: " + smsMessage.getId() + " successfully changed to " + statusType);
+                            }
+                        }
+                    }
+
+                    if (smsMessageDeliveryReportDatas.size() > 0) {
+                        logger.info(smsMessageDeliveryReportDatas.size() + " "
+                                + "delivery report(s) successfully received from the intermediate gateway - sms");
+                    }
+                }
+            }
+
+            catch (Exception e) {
+                logger.error(e.getMessage(), e);
+            }
+            page ++;
+            totalRecords = smsMessageInternalIds.getTotalFilteredRecords();
+        } while (page < totalRecords);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/911cab85/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/service/SmsReadPlatformService.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/service/SmsReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/service/SmsReadPlatformService.java
index db330da..68a9a99 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/service/SmsReadPlatformService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/service/SmsReadPlatformService.java
@@ -19,7 +19,11 @@
 package org.apache.fineract.infrastructure.sms.service;
 
 import java.util.Collection;
+import java.util.Date;
+import java.util.List;
 
+import org.apache.fineract.infrastructure.core.service.Page;
+import org.apache.fineract.infrastructure.core.service.SearchParameters;
 import org.apache.fineract.infrastructure.sms.data.SmsData;
 
 public interface SmsReadPlatformService {
@@ -27,4 +31,20 @@ public interface SmsReadPlatformService {
     Collection<SmsData> retrieveAll();
 
     SmsData retrieveOne(Long resourceId);
+
+    Collection<SmsData> retrieveAllPending(final Long campaignId, final Integer limit);
+
+    Collection<SmsData> retrieveAllSent(Integer limit);
+
+    Collection<SmsData> retrieveAllDelivered(Integer limit);
+
+    Collection<SmsData> retrieveAllFailed(Integer limit);
+
+    Page<SmsData> retrieveSmsByStatus(final Long campaignId, SearchParameters searchParameters, Integer status, Date dateFrom, Date dateTo);
+
+    List<Long> retrieveExternalIdsOfAllSent(Integer limit);
+
+    Page<Long> retrieveAllWaitingForDeliveryReport(Integer limit);
+
+    List<Long> retrieveAllPending(Integer limit);
 }

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/911cab85/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/service/SmsReadPlatformServiceImpl.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/service/SmsReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/service/SmsReadPlatformServiceImpl.java
index c4fd088..5ad0eac 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/service/SmsReadPlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/service/SmsReadPlatformServiceImpl.java
@@ -20,13 +20,22 @@ package org.apache.fineract.infrastructure.sms.service;
 
 import java.sql.ResultSet;
 import java.sql.SQLException;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
 import java.util.Collection;
+import java.util.Date;
+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.sms.data.SmsData;
 import org.apache.fineract.infrastructure.sms.domain.SmsMessageEnumerations;
+import org.apache.fineract.infrastructure.sms.domain.SmsMessageStatusType;
 import org.apache.fineract.infrastructure.sms.exception.SmsNotFoundException;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.dao.EmptyResultDataAccessException;
@@ -39,6 +48,7 @@ public class SmsReadPlatformServiceImpl implements SmsReadPlatformService {
 
     private final JdbcTemplate jdbcTemplate;
     private final SmsMapper smsRowMapper;
+    private final PaginationHelper<SmsData> paginationHelper = new PaginationHelper<>();
 
     @Autowired
     public SmsReadPlatformServiceImpl(final RoutingDataSource dataSource) {
@@ -58,8 +68,11 @@ public class SmsReadPlatformServiceImpl implements SmsReadPlatformService {
             sql.append("smo.staff_id as staffId, ");
             sql.append("smo.status_enum as statusId, ");
             sql.append("smo.mobile_no as mobileNo, ");
-            sql.append("smo.message as message ");
-            sql.append("from sms_messages_outbound smo");
+            sql.append("smo.message as message, ");
+            sql.append("smc.provider_id as providerId, ");
+            sql.append("smc.campaign_name as campaignName ");
+            sql.append("from sms_messages_outbound smo ");
+            sql.append("join sms_campaign smc on smc.id = smo.campaign_id ");
 
             this.schema = sql.toString();
         }
@@ -67,6 +80,10 @@ public class SmsReadPlatformServiceImpl implements SmsReadPlatformService {
         public String schema() {
             return this.schema;
         }
+        
+        public String tableName() {
+            return "sms_messages_outbound";
+    }
 
         @Override
         public SmsData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
@@ -82,7 +99,11 @@ public class SmsReadPlatformServiceImpl implements SmsReadPlatformService {
             final Integer statusId = JdbcSupport.getInteger(rs, "statusId");
             final EnumOptionData status = SmsMessageEnumerations.status(statusId);
 
-            return SmsData.instance(id, groupId, clientId, staffId, status, mobileNo, message);
+            final Long providerId = JdbcSupport.getLong(rs, "providerId");
+            
+            final String campaignName = rs.getString("campaignName");
+
+            return SmsData.instance(id, groupId, clientId, staffId, status, mobileNo, message, providerId, campaignName);
         }
     }
 
@@ -104,4 +125,122 @@ public class SmsReadPlatformServiceImpl implements SmsReadPlatformService {
             throw new SmsNotFoundException(resourceId);
         }
     }
+
+    @Override
+    public Collection<SmsData> retrieveAllPending(final Long campaignId, final Integer limit) {
+        final String sqlPlusLimit = (limit > 0) ? " limit 0, " + limit : "";
+        String sql = "select " + this.smsRowMapper.schema() + " where smo.status_enum = " + SmsMessageStatusType.PENDING.getValue();
+        if (campaignId != null) {
+            sql += " and smo.campaign_id = " + campaignId;
+        }
+
+        sql += sqlPlusLimit;
+
+        return this.jdbcTemplate.query(sql, this.smsRowMapper, new Object[] {});
+    }
+
+    @Override
+    public Collection<SmsData> retrieveAllSent(final Integer limit) {
+        final String sqlPlusLimit = (limit > 0) ? " limit 0, " + limit : "";
+        final String sql = "select " + this.smsRowMapper.schema() + " where smo.status_enum IN (" + SmsMessageStatusType.WAITING_FOR_DELIVERY_REPORT.getValue()
+                + "," + SmsMessageStatusType.SENT.getValue() + ")" + sqlPlusLimit;
+
+        return this.jdbcTemplate.query(sql, this.smsRowMapper, new Object[] {});
+    }
+
+    @Override
+    public List<Long> retrieveExternalIdsOfAllSent(final Integer limit) {
+        final String sqlPlusLimit = (limit > 0) ? " limit 0, " + limit : "";
+        final String sql = "select external_id from " + this.smsRowMapper.tableName() + " where status_enum = "
+                + SmsMessageStatusType.SENT.getValue() + sqlPlusLimit;
+
+        return this.jdbcTemplate.queryForList(sql, Long.class);
+    }
+
+    @Override
+    public Page<Long> retrieveAllWaitingForDeliveryReport(final Integer limit) {
+        final String sqlPlusLimit = (limit > 0) ? " limit 0, " + limit : "";
+        final String sql = "select id from " + this.smsRowMapper.tableName() + " where status_enum = "
+                + SmsMessageStatusType.WAITING_FOR_DELIVERY_REPORT.getValue() + sqlPlusLimit;
+        final String sqlCountRows = "SELECT FOUND_ROWS()";
+        return this.paginationHelper.fetchPage(jdbcTemplate, sql, sqlCountRows, Long.class); 
+        //(this.jdbcTemplate, sqlCountRows, new Object [] {}, Long.class); this.jdbcTemplate.queryForList(sql, Long.class);
+    }
+
+    @Override
+    public List<Long> retrieveAllPending(final Integer limit) {
+        final String sqlPlusLimit = (limit > 0) ? " limit 0, " + limit : "";
+        final String sql = "select external_id from " + this.smsRowMapper.tableName() + " where status_enum = "
+                + SmsMessageStatusType.PENDING.getValue() + sqlPlusLimit;
+
+        return this.jdbcTemplate.queryForList(sql, Long.class);
+    }
+
+    @Override
+    public Collection<SmsData> retrieveAllDelivered(final Integer limit) {
+        final String sqlPlusLimit = (limit > 0) ? " limit 0, " + limit : "";
+        final String sql = "select " + this.smsRowMapper.schema() + " where smo.status_enum = " + SmsMessageStatusType.DELIVERED.getValue()
+                + sqlPlusLimit;
+
+        return this.jdbcTemplate.query(sql, this.smsRowMapper, new Object[] {});
+    }
+
+    @Override
+    public Collection<SmsData> retrieveAllFailed(final Integer limit) {
+        final String sqlPlusLimit = (limit > 0) ? " limit 0, " + limit : "";
+        final String sql = "select " + this.smsRowMapper.schema() + " where smo.status_enum = " + SmsMessageStatusType.FAILED.getValue()
+                + sqlPlusLimit;
+
+        return this.jdbcTemplate.query(sql, this.smsRowMapper, new Object[] {});
+    }
+
+    @Override
+    public Page<SmsData> retrieveSmsByStatus(final Long campaignId, final SearchParameters searchParameters, final Integer status, final Date dateFrom, final Date dateTo) {
+        final StringBuilder sqlBuilder = new StringBuilder(200);
+        final Object[] objectArray = new Object[10];
+        int arrayPos = 0;
+        sqlBuilder.append("select SQL_CALC_FOUND_ROWS ");
+        sqlBuilder.append(this.smsRowMapper.schema());
+        if (status != null) {
+            sqlBuilder.append(" where smo.campaign_id = ? and smo.status_enum= ? ");
+            objectArray[arrayPos] = campaignId;
+            arrayPos = arrayPos + 1;
+            objectArray[arrayPos] = status;
+            arrayPos = arrayPos + 1;
+        }
+        String fromDateString = null;
+        String toDateString = null;
+        if (dateFrom != null && dateTo != null) {
+            final DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
+            fromDateString = df.format(dateFrom);
+            toDateString = df.format(dateTo);
+            sqlBuilder.append(" and smo.submittedon_date >= ? and smo.submittedon_date <= ? ");
+            objectArray[arrayPos] = fromDateString;
+            arrayPos = arrayPos + 1;
+            
+            objectArray[arrayPos] = toDateString;
+            arrayPos = arrayPos + 1;
+        }
+
+        if (searchParameters.isOrderByRequested()) {
+            sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
+
+            if (searchParameters.isSortOrderProvided()) {
+                sqlBuilder.append(' ').append(searchParameters.getSortOrder());
+            }
+        } else {
+            sqlBuilder.append(" order by smo.submittedon_date, smo.id");
+        }
+
+        if (searchParameters.isLimited()) {
+            sqlBuilder.append(" limit ").append(searchParameters.getLimit());
+            if (searchParameters.isOffset()) {
+                sqlBuilder.append(" offset ").append(searchParameters.getOffset());
+            }
+        }
+        final String sqlCountRows = "SELECT FOUND_ROWS()";
+        final Object[] finalObjectArray = Arrays.copyOf(objectArray, arrayPos);
+        return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlCountRows, sqlBuilder.toString(), finalObjectArray, this.smsRowMapper);
+    }
+
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/911cab85/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 0530452..5e82970 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
@@ -46,6 +46,7 @@ import org.apache.fineract.infrastructure.codes.domain.CodeValue;
 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.domain.AbstractPersistableCustom;
 import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
 import org.apache.fineract.infrastructure.core.service.DateUtils;
 import org.apache.fineract.infrastructure.documentmanagement.domain.Image;
@@ -57,7 +58,6 @@ import org.apache.fineract.portfolio.group.domain.Group;
 import org.apache.fineract.useradministration.domain.AppUser;
 import org.joda.time.LocalDate;
 import org.joda.time.format.DateTimeFormatter;
-import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
 
 @Entity
 @Table(name = "m_client", uniqueConstraints = { @UniqueConstraint(columnNames = { "account_no" }, name = "account_no_UNIQUE"), //
@@ -1004,4 +1004,10 @@ public final class Client extends AbstractPersistableCustom<Long> {
     public void loadLazyCollections() {
         this.groups.size() ;
     }
+    
+    public String getFirstname(){return this.firstname;}
+
+    public String getMiddlename(){return this.middlename;}
+
+    public String getLastname(){return this.lastname;}
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/911cab85/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientTransaction.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientTransaction.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientTransaction.java
index c8e7266..b7656f8 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientTransaction.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientTransaction.java
@@ -205,6 +205,10 @@ public class ClientTransaction extends AbstractPersistableCustom<Long> {
         return client.getId();
     }
 
+    public Client getClient() {
+        return this.client ;
+    }
+    
     public Money getAmount() {
         return Money.of(getCurrency(), this.amount);
     }

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/911cab85/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java
index 3c620ab..5db7a02 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java
@@ -19,6 +19,7 @@
 package org.apache.fineract.portfolio.client.service;
 
 
+import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
@@ -63,6 +64,9 @@ import org.apache.fineract.portfolio.client.exception.ClientHasNoStaffException;
 import org.apache.fineract.portfolio.client.exception.ClientMustBePendingToBeDeletedException;
 import org.apache.fineract.portfolio.client.exception.InvalidClientSavingProductException;
 import org.apache.fineract.portfolio.client.exception.InvalidClientStateTransitionException;
+import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_ENTITY;
+import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS;
+import org.apache.fineract.portfolio.common.service.BusinessEventNotifierService;
 import org.apache.fineract.portfolio.group.domain.Group;
 import org.apache.fineract.portfolio.group.domain.GroupRepository;
 import org.apache.fineract.portfolio.group.exception.GroupMemberCountNotInPermissibleRangeException;
@@ -113,13 +117,15 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
     private final CommandProcessingService commandProcessingService;
     private final ConfigurationDomainService configurationDomainService;
     private final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository;
-	private final FromJsonHelper fromApiJsonHelper;
-	private final ConfigurationReadPlatformService configurationReadPlatformService;
-	private final AddressWritePlatformService addressWritePlatformService;
+    private final FromJsonHelper fromApiJsonHelper;
+    private final ConfigurationReadPlatformService configurationReadPlatformService;
+    private final AddressWritePlatformService addressWritePlatformService;
+    private final BusinessEventNotifierService businessEventNotifierService;
 
     @Autowired
     public ClientWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
-            final ClientRepositoryWrapper clientRepository, final ClientNonPersonRepositoryWrapper clientNonPersonRepository, final OfficeRepositoryWrapper officeRepositoryWrapper, final NoteRepository noteRepository,
+            final ClientRepositoryWrapper clientRepository, final ClientNonPersonRepositoryWrapper clientNonPersonRepository,
+            final OfficeRepositoryWrapper officeRepositoryWrapper, final NoteRepository noteRepository,
             final ClientDataValidator fromApiJsonDeserializer, final AccountNumberGenerator accountNumberGenerator,
             final GroupRepository groupRepository, final StaffRepositoryWrapper staffRepository,
             final CodeValueRepositoryWrapper codeValueRepository, final LoanRepositoryWrapper loanRepositoryWrapper,
@@ -128,7 +134,7 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
             final CommandProcessingService commandProcessingService, final ConfigurationDomainService configurationDomainService,
             final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository, final FromJsonHelper fromApiJsonHelper,
             final ConfigurationReadPlatformService configurationReadPlatformService,
-			final AddressWritePlatformService addressWritePlatformService) {
+            final AddressWritePlatformService addressWritePlatformService, final BusinessEventNotifierService businessEventNotifierService) {
         this.context = context;
         this.clientRepository = clientRepository;
         this.clientNonPersonRepository = clientNonPersonRepository;
@@ -147,8 +153,9 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
         this.configurationDomainService = configurationDomainService;
         this.accountNumberFormatRepository = accountNumberFormatRepository;
         this.fromApiJsonHelper = fromApiJsonHelper;
-    	this.configurationReadPlatformService = configurationReadPlatformService;
-		this.addressWritePlatformService = addressWritePlatformService;
+        this.configurationReadPlatformService = configurationReadPlatformService;
+        this.addressWritePlatformService = addressWritePlatformService;
+        this.businessEventNotifierService = businessEventNotifierService;
     }
 
     @Transactional
@@ -158,7 +165,8 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
         final Client client = this.clientRepository.findOneWithNotFoundDetection(clientId);
 
         if (client.isNotPending()) { throw new ClientMustBePendingToBeDeletedException(clientId); }
-
+        this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.CLIENTS_DELETE,
+                constructEntityMap(BUSINESS_ENTITY.CLIENT, client));
         final List<Note> relatedNotes = this.noteRepository.findByClientId(clientId);
         this.noteRepository.deleteInBatch(relatedNotes);
 
@@ -167,7 +175,8 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
         	this.clientNonPersonRepository.delete(clientNonPerson);
         
         this.clientRepository.delete(client);
-
+        this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.CLIENTS_DELETE,
+                constructEntityMap(BUSINESS_ENTITY.CLIENT, client));
         return new CommandProcessingResultBuilder() //
                 .withOfficeId(client.officeId()) //
                 .withClientId(clientId) //
@@ -275,6 +284,8 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
             
             final Client newClient = Client.createNew(currentUser, clientOffice, clientParentGroup, staff, savingsProductId, gender,
                     clientType, clientClassification, legalFormValue, command);
+            this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.CLIENTS_CREATE,
+                    constructEntityMap(BUSINESS_ENTITY.CLIENT, newClient));
             boolean rollbackTransaction = false;
             if (newClient.isActive()) {
                 validateParentGroupRulesBeforeClientActivation(newClient);
@@ -306,7 +317,8 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
                 this.addressWritePlatformService.addNewClientAddress(newClient, command);
             }
 
-
+            this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.CLIENTS_CREATE,
+                    constructEntityMap(BUSINESS_ENTITY.CLIENT, newClient));
             return new CommandProcessingResultBuilder() //
                     .withCommandId(command.commandId()) //
                     .withOfficeId(clientOffice.getId()) //
@@ -523,7 +535,8 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
 
             final Client client = this.clientRepository.findOneWithNotFoundDetection(clientId, true);
             validateParentGroupRulesBeforeClientActivation(client);
-
+            this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.CLIENTS_ACTIVATE,
+                    constructEntityMap(BUSINESS_ENTITY.CLIENT, client));
             final Locale locale = command.extractLocale();
             final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
             final LocalDate activationDate = command.localDateValueOfParameterNamed("activationDate");
@@ -532,7 +545,8 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
             client.activate(currentUser, fmt, activationDate);
             CommandProcessingResult result = openSavingsAccount(client, fmt);
             this.clientRepository.saveAndFlush(client);
-
+            this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.CLIENTS_ACTIVATE,
+                    constructEntityMap(BUSINESS_ENTITY.CLIENT, client));
             return new CommandProcessingResultBuilder() //
                     .withCommandId(command.commandId()) //
                     .withOfficeId(client.officeId()) //
@@ -609,7 +623,8 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
         this.fromApiJsonDeserializer.validateForAssignStaff(command.json());
 
         final Client clientForUpdate = this.clientRepository.findOneWithNotFoundDetection(clientId);
-
+        this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.CLIENTS_ASSIGN_STAFF,
+                constructEntityMap(BUSINESS_ENTITY.CLIENT, clientForUpdate));
         Staff staff = null;
         final Long staffId = command.longValueOfParameterNamed(ClientApiConstants.staffIdParamName);
         if (staffId != null) {
@@ -625,7 +640,8 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
         this.clientRepository.saveAndFlush(clientForUpdate);
 
         actualChanges.put(ClientApiConstants.staffIdParamName, staffId);
-
+        this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.CLIENTS_ASSIGN_STAFF,
+                constructEntityMap(BUSINESS_ENTITY.CLIENT, clientForUpdate));
         return new CommandProcessingResultBuilder() //
                 .withCommandId(command.commandId()) //
                 .withOfficeId(clientForUpdate.officeId()) //
@@ -644,6 +660,8 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
             this.fromApiJsonDeserializer.validateClose(command);
 
             final Client client = this.clientRepository.findOneWithNotFoundDetection(clientId);
+            this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.CLIENTS_CLOSE,
+                    constructEntityMap(BUSINESS_ENTITY.CLIENT, client));
             final LocalDate closureDate = command.localDateValueOfParameterNamed(ClientApiConstants.closureDateParamName);
             final Long closureReasonId = command.longValueOfParameterNamed(ClientApiConstants.closureReasonIdParamName);
 
@@ -689,7 +707,8 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
 
             client.close(currentUser, closureReason, closureDate.toDate());
             this.clientRepository.saveAndFlush(client);
-
+            this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.CLIENTS_CLOSE,
+                    constructEntityMap(BUSINESS_ENTITY.CLIENT, client));
             return new CommandProcessingResultBuilder() //
                     .withCommandId(command.commandId()) //
                     .withClientId(clientId) //
@@ -763,6 +782,8 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
         this.fromApiJsonDeserializer.validateRejection(command);
 
         final Client client = this.clientRepository.findOneWithNotFoundDetection(entityId);
+        this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.CLIENTS_REJECT,
+                constructEntityMap(BUSINESS_ENTITY.CLIENT, client));
         final LocalDate rejectionDate = command.localDateValueOfParameterNamed(ClientApiConstants.rejectionDateParamName);
         final Long rejectionReasonId = command.longValueOfParameterNamed(ClientApiConstants.rejectionReasonIdParamName);
 
@@ -780,7 +801,8 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
         }
         client.reject(currentUser, rejectionReason, rejectionDate.toDate());
         this.clientRepository.saveAndFlush(client);
-
+        this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.CLIENTS_REJECT,
+                constructEntityMap(BUSINESS_ENTITY.CLIENT, client));
         return new CommandProcessingResultBuilder() //
                 .withCommandId(command.commandId()) //
                 .withClientId(entityId) //
@@ -794,6 +816,8 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
         this.fromApiJsonDeserializer.validateWithdrawn(command);
 
         final Client client = this.clientRepository.findOneWithNotFoundDetection(entityId);
+        this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.CLIENTS_WITHDRAW,
+                constructEntityMap(BUSINESS_ENTITY.CLIENT, client));
         final LocalDate withdrawalDate = command.localDateValueOfParameterNamed(ClientApiConstants.withdrawalDateParamName);
         final Long withdrawalReasonId = command.longValueOfParameterNamed(ClientApiConstants.withdrawalReasonIdParamName);
 
@@ -811,7 +835,8 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
         }
         client.withdraw(currentUser, withdrawalReason, withdrawalDate.toDate());
         this.clientRepository.saveAndFlush(client);
-
+        this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.CLIENTS_WITHDRAW,
+                constructEntityMap(BUSINESS_ENTITY.CLIENT, client));
         return new CommandProcessingResultBuilder() //
                 .withCommandId(command.commandId()) //
                 .withClientId(entityId) //
@@ -825,6 +850,8 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
         this.fromApiJsonDeserializer.validateReactivate(command);
 
         final Client client = this.clientRepository.findOneWithNotFoundDetection(entityId);
+        this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.CLIENTS_REACTIVATE,
+                constructEntityMap(BUSINESS_ENTITY.CLIENT, client));
         final LocalDate reactivateDate = command.localDateValueOfParameterNamed(ClientApiConstants.reactivationDateParamName);
 
         if (!client.isClosed()) {
@@ -837,7 +864,8 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
         }
         client.reActivate(currentUser, reactivateDate.toDate());
         this.clientRepository.saveAndFlush(client);
-
+        this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.CLIENTS_REACTIVATE,
+                constructEntityMap(BUSINESS_ENTITY.CLIENT, client));
         return new CommandProcessingResultBuilder() //
                 .withCommandId(command.commandId()) //
                 .withClientId(entityId) //
@@ -899,4 +927,10 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
 				.withEntityId(entityId) //
 				.build();
 	}
+
+    private Map<BUSINESS_ENTITY, Object> constructEntityMap(final BUSINESS_ENTITY entityEvent, Object entity) {
+        Map<BUSINESS_ENTITY, Object> map = new HashMap<>(1);
+        map.put(entityEvent, entity);
+        return map;
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/911cab85/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/BusinessEventNotificationConstants.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/BusinessEventNotificationConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/BusinessEventNotificationConstants.java
index b5f5258..9532b52 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/BusinessEventNotificationConstants.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/BusinessEventNotificationConstants.java
@@ -24,15 +24,25 @@ import java.util.Set;
 public class BusinessEventNotificationConstants {
 
     public static enum BUSINESS_EVENTS {
-        LOAN_APPROVED("loan_approved"), LOAN_UNDO_APPROVAL("loan_undo_approval"), LOAN_UNDO_DISBURSAL("loan_undo_disbursal"), LOAN_UNDO_LASTDISBURSAL("loan_undo_lastdisbursal"),LOAN_UNDO_TRANSACTION(
-                "loan_undo_transaction"), LOAN_ADJUST_TRANSACTION("loan_adjust_transaction"), LOAN_MAKE_REPAYMENT(
-                "loan_repayment_transaction"), LOAN_WRITTEN_OFF("loan_writtenoff"), LOAN_UNDO_WRITTEN_OFF("loan_undo_writtenoff"), LOAN_DISBURSAL(
-                "loan_disbursal"), LOAN_WAIVE_INTEREST("loan_waive_interest"), LOAN_CLOSE("loan_close"), LOAN_CLOSE_AS_RESCHEDULE(
-                "loan_close_as_reschedule"), LOAN_ADD_CHARGE("loan_add_charge"), LOAN_UPDATE_CHARGE("loan_update_charge"), LOAN_WAIVE_CHARGE(
-                "loan_waive_charge"), LOAN_DELETE_CHARGE("loan_delete_charge"), LOAN_CHARGE_PAYMENT("loan_charge_payment"), LOAN_INITIATE_TRANSFER(
-                "loan_initiate_transfer"), LOAN_ACCEPT_TRANSFER("loan_accept_transfer"), LOAN_WITHDRAW_TRANSFER("loan_withdraw_transfer"), LOAN_REJECT_TRANSFER(
-                "loan_reject_transfer"), LOAN_REASSIGN_OFFICER("loan_reassign_officer"), LOAN_REMOVE_OFFICER("loan_remove_officer"), LOAN_APPLY_OVERDUE_CHARGE(
-                "loan_apply_overdue_charge"), LOAN_INTEREST_RECALCULATION("loan_interest_recalculation"), LOAN_REFUND("loan_refund"), LOAN_FORECLOSURE("loan_foreclosure");
+        LOAN_APPROVED("loan_approved"), LOAN_REJECTED("loan_reject"), LOAN_UNDO_APPROVAL("loan_undo_approval"), LOAN_UNDO_DISBURSAL("loan_undo_disbursal"), LOAN_UNDO_LASTDISBURSAL(
+                "loan_undo_lastdisbursal"), LOAN_UNDO_TRANSACTION("loan_undo_transaction"), LOAN_ADJUST_TRANSACTION(
+                "loan_adjust_transaction"), LOAN_MAKE_REPAYMENT("loan_repayment_transaction"), LOAN_WRITTEN_OFF("loan_writtenoff"), LOAN_UNDO_WRITTEN_OFF(
+                "loan_undo_writtenoff"), LOAN_DISBURSAL("loan_disbursal"), LOAN_WAIVE_INTEREST("loan_waive_interest"), LOAN_CLOSE(
+                "loan_close"), LOAN_CLOSE_AS_RESCHEDULE("loan_close_as_reschedule"), LOAN_ADD_CHARGE("loan_add_charge"), LOAN_UPDATE_CHARGE(
+                "loan_update_charge"), LOAN_WAIVE_CHARGE("loan_waive_charge"), LOAN_DELETE_CHARGE("loan_delete_charge"), LOAN_CHARGE_PAYMENT(
+                "loan_charge_payment"), LOAN_INITIATE_TRANSFER("loan_initiate_transfer"), LOAN_ACCEPT_TRANSFER("loan_accept_transfer"), LOAN_WITHDRAW_TRANSFER(
+                "loan_withdraw_transfer"), LOAN_REJECT_TRANSFER("loan_reject_transfer"), LOAN_REASSIGN_OFFICER("loan_reassign_officer"), LOAN_REMOVE_OFFICER(
+                "loan_remove_officer"), LOAN_APPLY_OVERDUE_CHARGE("loan_apply_overdue_charge"), LOAN_INTEREST_RECALCULATION(
+                "loan_interest_recalculation"), LOAN_REFUND("loan_refund"), LOAN_FORECLOSURE("loan_foreclosure"), SAVINGS_ACTIVATE(
+                "savings_activated"), SAVINGS_ADJUST_TRANSACTION("savings_adjust_transaction"), SAVINGS_APPLY_ANNUAL_FEE(
+                "savings_apply_annual_fee"), SAVINGS_CALCULATE_INTEREST("savings_calculate_interest"), SAVINGS_CLOSE("savings_close"), SAVINGS_POST_INTEREST(
+                "savings_post_interest"), SAVINGS_REJECT("savings_reject"), SAVINGS_DEPOSIT("savings_deposit"), SAVINGS_WITHDRAWAL(
+                "savings_withdrawal"), SAVINGS_UNDO("savings_undo"), SAVINGS_ADD_CHARGE("savings_add_charge"), SAVINGS_WAIVE_CHARGE(
+                "savings_waive_charge"), SAVINGS_PAY_CHARGE("savings_pay_charge"), CLIENTS_ACCEPT_TRANSFER("clients_accept_transfer"), CLIENTS_ACTIVATE(
+                "clients_activate"), CLIENTS_ASSIGN_STAFF("clients_assign_staff"), CLIENTS_CLOSE("clients_close"), CLIENTS_CREATE(
+                "clients_create"), CLIENTS_DELETE("clients_delete"), CLIENTS_PROPOSE_TRANSFER("clients_propose_transfer"), CLIENTS_REACTIVATE(
+                "clients_reactivate"), CLIENTS_REJECT("clients_reject"), CLIENTS_REJECT_TRANSFER("clients_reject_transfer"), CLIENTS_WITHDRAW(
+                "clients_withdraw"), CLIENTS_WITHDRAW_TRANSFER("clients_withdraw_transfer");
 
         private final String value;
 
@@ -58,7 +68,7 @@ public class BusinessEventNotificationConstants {
 
     public static enum BUSINESS_ENTITY {
         LOAN("loan"), LOAN_TRANSACTION("loan_transaction"), LOAN_CHARGE("loan_charge"), LOAN_ADJUSTED_TRANSACTION(
-                "loan_adjusted_transaction");
+                "loan_adjusted_transaction"), SAVING("saving"), CLIENT("client"), SAVINGS_TRANSACTION("Savings Transaction");
 
         private final String value;
 
@@ -70,4 +80,5 @@ public class BusinessEventNotificationConstants {
             return this.value;
         }
     }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/911cab85/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java
index 0b6fbf1..c236e37 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java
@@ -358,7 +358,7 @@ public class LoansApiResource {
     public String retrieveLoan(@PathParam("loanId") final Long loanId,
             @DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly,
             @Context final UriInfo uriInfo) {
-
+        long start = System.currentTimeMillis() ;
         this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
 
         LoanAccountData loanBasicDetails = this.loanReadPlatformService.retrieveOne(loanId);
@@ -392,9 +392,7 @@ public class LoansApiResource {
             if(calendarData != null)
             	loanBasicDetails = LoanAccountData.withLoanCalendarData(loanBasicDetails, calendarData);
         }
-
         Collection<InterestRatePeriodData> interestRatesPeriods = this.loanReadPlatformService.retrieveLoanInterestRatePeriodData(loanBasicDetails);
-
         Collection<LoanTransactionData> loanRepayments = null;
         LoanScheduleData repaymentSchedule = null;
         Collection<LoanChargeData> charges = null;
@@ -597,7 +595,13 @@ public class LoansApiResource {
 
         final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters(),
                 mandatoryResponseParameters);
-        return this.toApiJsonSerializer.serialize(settings, loanAccount, this.LOAN_DATA_PARAMETERS);
+        long end = System.currentTimeMillis() ;
+        System.out.println("LoansApiResource.retrieveLoan() Time took: "+(end-start));
+        start = System.currentTimeMillis() ;
+        String toReturn = this.toApiJsonSerializer.serialize(settings, loanAccount, this.LOAN_DATA_PARAMETERS);
+        end = System.currentTimeMillis() ;
+        System.out.println("LoansApiResource.retrieveLoan() Time took to Serialize: "+(end-start));
+        return toReturn ;
     }
 
     @GET

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/911cab85/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
index c1b0f2c..15e23cb 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
@@ -6389,7 +6389,6 @@ public class Loan extends AbstractPersistableCustom<Long> {
     public Collection<LoanCharge> getLoanCharges() {
         return this.charges;
     }
-    
     public void initializeLazyCollections() {
         this.charges.size() ;
         this.trancheCharges.size() ;
@@ -6412,4 +6411,9 @@ public class Loan extends AbstractPersistableCustom<Long> {
     public void initializeRepaymentSchedule() {
         this.repaymentScheduleInstallments.size() ;
     }
+    public boolean hasInvalidLoanType() {
+        return AccountType.fromInt(this.loanType).isInvalid();
+    }
+    
+    public boolean isIndividualLoan(){return AccountType.fromInt(this.loanType).isIndividualAccount();}
 }

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/911cab85/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
index 3dc1cb7..0da6393 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
@@ -778,6 +778,14 @@ public class LoanTransaction extends AbstractPersistableCustom<Long> {
         return isAccrual();
     }
     
+    public BigDecimal getOutstandingLoanBalance() {
+        return outstandingLoanBalance;
+    }
+    
+    public PaymentDetail getPaymentDetail() {
+        return this.paymentDetail;
+    }
+    
     public boolean isPaymentTransaction() {
         return this.isNotReversed()
                 && !(this.isDisbursement() || this.isAccrual() || this.isRepaymentAtDisbursement() || this.isNonMonetaryTransaction() || this

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/911cab85/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/InvalidLoanTypeException.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/InvalidLoanTypeException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/InvalidLoanTypeException.java
new file mode 100644
index 0000000..6c64060
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/exception/InvalidLoanTypeException.java
@@ -0,0 +1,32 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+/**
+ * {@link AbstractPlatformDomainRuleException} thrown an action to transition a
+ * loan from one state to another violates a domain rule.
+ */
+public class InvalidLoanTypeException extends AbstractPlatformDomainRuleException {
+
+    public InvalidLoanTypeException(final String defaultUserMessage, final Object... defaultUserMessageArgs) {
+        super("error.msg.loan.type.invalid", defaultUserMessage, defaultUserMessageArgs);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/911cab85/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java
index b697797..221273f 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java
@@ -1233,7 +1233,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
                 this.noteRepository.save(note);
             }
         }
-
+        this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.LOAN_REJECTED, constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
         return new CommandProcessingResultBuilder() //
                 .withCommandId(command.commandId()) //
                 .withEntityId(loan.getId()) //

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/911cab85/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
index 75a44ea..46d9795 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
@@ -392,7 +392,6 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
         String sql = "select " + mapper.schema() + " where l.id =?";
         LoanTransactionData loanTransactionData = this.jdbcTemplate.queryForObject(sql, mapper, LoanTransactionType.REPAYMENT.getValue(),
                 loanId, loanId);
-
         final Collection<PaymentTypeData> paymentOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
         return LoanTransactionData.templateOnTop(loanTransactionData, paymentOptions);
     }

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/911cab85/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/domain/PaymentDetail.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/domain/PaymentDetail.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/domain/PaymentDetail.java
index c02bc9d..be39011 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/domain/PaymentDetail.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/domain/PaymentDetail.java
@@ -115,4 +115,5 @@ public final class PaymentDetail extends AbstractPersistableCustom<Long> {
         return this.paymentType;
     }
 
+    public String getReceiptNumber() { return this.receiptNumber; }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/911cab85/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java
index 6426026..ae1b0f4 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java
@@ -25,6 +25,9 @@ import org.apache.fineract.infrastructure.security.service.PlatformSecurityConte
 import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
 import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper;
 import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_ENTITY;
+import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS;
+import org.apache.fineract.portfolio.common.service.BusinessEventNotifierService;
 import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
 import org.apache.fineract.portfolio.savings.SavingsAccountTransactionType;
 import org.apache.fineract.portfolio.savings.SavingsTransactionBooleanValues;
@@ -40,6 +43,7 @@ import org.springframework.transaction.annotation.Transactional;
 import java.math.BigDecimal;
 import java.math.MathContext;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -55,6 +59,7 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi
     private final JournalEntryWritePlatformService journalEntryWritePlatformService;
     private final ConfigurationDomainService configurationDomainService;
     private final DepositAccountOnHoldTransactionRepository depositAccountOnHoldTransactionRepository;
+    private final BusinessEventNotifierService businessEventNotifierService;
 
     @Autowired
     public SavingsAccountDomainServiceJpa(final SavingsAccountRepositoryWrapper savingsAccountRepository,
@@ -62,7 +67,8 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi
             final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper,
             final JournalEntryWritePlatformService journalEntryWritePlatformService,
             final ConfigurationDomainService configurationDomainService, final PlatformSecurityContext context,
-            final DepositAccountOnHoldTransactionRepository depositAccountOnHoldTransactionRepository) {
+            final DepositAccountOnHoldTransactionRepository depositAccountOnHoldTransactionRepository, 
+            final BusinessEventNotifierService businessEventNotifierService) {
         this.savingsAccountRepository = savingsAccountRepository;
         this.savingsAccountTransactionRepository = savingsAccountTransactionRepository;
         this.applicationCurrencyRepositoryWrapper = applicationCurrencyRepositoryWrapper;
@@ -70,6 +76,7 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi
         this.configurationDomainService = configurationDomainService;
         this.context = context;
         this.depositAccountOnHoldTransactionRepository = depositAccountOnHoldTransactionRepository;
+        this.businessEventNotifierService = businessEventNotifierService;
     }
 
     @Transactional
@@ -82,9 +89,10 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi
         final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
                 .isSavingsInterestPostingAtCurrentPeriodEnd();
         final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
-
         if (transactionBooleanValues.isRegularTransaction() && !account.allowWithdrawal()) { throw new DepositAccountTransactionNotAllowedException(
                 account.getId(), "withdraw", account.depositAccountType()); }
+        this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.SAVINGS_WITHDRAWAL,
+                constructEntityMap(BUSINESS_ENTITY.SAVING, account));
         final Set<Long> existingTransactionIds = new HashSet<>();
         final boolean interestPostAsOn = false;
         final Set<Long> existingReversedTransactionIds = new HashSet<>();
@@ -92,7 +100,6 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi
         final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount,
                 paymentDetail, new Date(), user);
         final SavingsAccountTransaction withdrawal = account.withdraw(transactionDTO, transactionBooleanValues.isApplyWithdrawFee());
-
         final MathContext mc = MathContext.DECIMAL64;
         if (account.isBeforeLastPostingPeriod(transactionDate)) {
             final LocalDate today = DateUtils.getLocalDateOfTenant();
@@ -114,7 +121,8 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi
         this.savingsAccountRepository.save(account);
 
         postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, transactionBooleanValues.isAccountTransfer());
-
+        this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.SAVINGS_WITHDRAWAL,
+                constructEntityMap(BUSINESS_ENTITY.SAVING, account));
         return withdrawal;
     }
 
@@ -147,7 +155,8 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi
 
         if (isRegularTransaction && !account.allowDeposit()) { throw new DepositAccountTransactionNotAllowedException(account.getId(),
                 "deposit", account.depositAccountType()); }
-
+        this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.SAVINGS_DEPOSIT,
+                constructEntityMap(BUSINESS_ENTITY.SAVING, account));
         boolean isInterestTransfer = false;
         final Set<Long> existingTransactionIds = new HashSet<>();
         final Set<Long> existingReversedTransactionIds = new HashSet<>();
@@ -172,7 +181,8 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi
         this.savingsAccountRepository.save(account);
 
         postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
-
+        this.businessEventNotifierService.notifyBusinessEventWasExecuted(BUSINESS_EVENTS.SAVINGS_DEPOSIT,
+                constructEntityMap(BUSINESS_ENTITY.SAVING, account));
         return deposit;
     }
 
@@ -218,4 +228,10 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi
         final boolean isAccountTransfer = false;
         postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
     }
+
+    private Map<BUSINESS_ENTITY, Object> constructEntityMap(final BUSINESS_ENTITY entityEvent, Object entity) {
+        Map<BUSINESS_ENTITY, Object> map = new HashMap<>(1);
+        map.put(entityEvent, entity);
+        return map;
+    }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/911cab85/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java
index 56e6485..c623709 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java
@@ -729,16 +729,15 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom<L
     public void updateAmount(final Money amount) {
         this.amount = amount.getAmount();
     }
-    
+
     public Integer getTypeOf() {
         return this.typeOf;
     }
-    
+
     public SavingsAccount getSavingsAccount() {
         return this.savingsAccount;
     }
 
-    
     public void setSavingsAccount(SavingsAccount savingsAccount) {
         this.savingsAccount = savingsAccount;
     }
@@ -747,4 +746,7 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom<L
         return this.dateOf;
     }
 
+    public PaymentDetail getPaymentDetail() {
+    	return this.paymentDetail ;
+    }
 }
\ No newline at end of file