You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by na...@apache.org on 2016/05/04 09:51:09 UTC

[1/2] incubator-fineract git commit: Customer Self Service : APIs for Third Party Beneficiary management and Third Party Account Transfers

Repository: incubator-fineract
Updated Branches:
  refs/heads/develop 9d4d90121 -> 2bd3b0625


http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/2bd3b062/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/api/SelfAccountTransferApiResource.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/api/SelfAccountTransferApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/api/SelfAccountTransferApiResource.java
index 60b6e07..dad8da9 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/api/SelfAccountTransferApiResource.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/api/SelfAccountTransferApiResource.java
@@ -18,27 +18,37 @@
  */
 package org.apache.fineract.portfolio.self.account.api;
 
+import java.math.BigDecimal;
 import java.util.Collection;
+import java.util.Map;
 
 import javax.ws.rs.Consumes;
+import javax.ws.rs.DefaultValue;
 import javax.ws.rs.GET;
 import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.UriInfo;
 
+import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
 import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
 import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
 import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
 import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
 import org.apache.fineract.portfolio.account.api.AccountTransfersApiResource;
+import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformService;
 import org.apache.fineract.portfolio.self.account.data.SelfAccountTemplateData;
 import org.apache.fineract.portfolio.self.account.data.SelfAccountTransferData;
 import org.apache.fineract.portfolio.self.account.data.SelfAccountTransferDataValidator;
+import org.apache.fineract.portfolio.self.account.exception.BeneficiaryTransferLimitExceededException;
+import org.apache.fineract.portfolio.self.account.exception.DailyTPTTransactionAmountLimitExceededException;
 import org.apache.fineract.portfolio.self.account.service.SelfAccountTransferReadService;
+import org.apache.fineract.portfolio.self.account.service.SelfBeneficiariesTPTReadPlatformService;
 import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.LocalDate;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Scope;
 import org.springframework.stereotype.Component;
@@ -54,6 +64,9 @@ public class SelfAccountTransferApiResource {
 	private final SelfAccountTransferReadService selfAccountTransferReadService;
 	private final ApiRequestParameterHelper apiRequestParameterHelper;
 	private final SelfAccountTransferDataValidator dataValidator;
+	private final SelfBeneficiariesTPTReadPlatformService tptBeneficiaryReadPlatformService;
+	private final ConfigurationDomainService configurationDomainService;
+	private final AccountTransfersReadPlatformService accountTransfersReadPlatformService;
 
 	@Autowired
 	public SelfAccountTransferApiResource(
@@ -62,37 +75,101 @@ public class SelfAccountTransferApiResource {
 			final AccountTransfersApiResource accountTransfersApiResource,
 			final SelfAccountTransferReadService selfAccountTransferReadService,
 			final ApiRequestParameterHelper apiRequestParameterHelper,
-			final SelfAccountTransferDataValidator dataValidator) {
+			final SelfAccountTransferDataValidator dataValidator,
+			final SelfBeneficiariesTPTReadPlatformService tptBeneficiaryReadPlatformService,
+			final ConfigurationDomainService configurationDomainService,
+			final AccountTransfersReadPlatformService accountTransfersReadPlatformService) {
 		this.context = context;
 		this.toApiJsonSerializer = toApiJsonSerializer;
 		this.accountTransfersApiResource = accountTransfersApiResource;
 		this.selfAccountTransferReadService = selfAccountTransferReadService;
 		this.apiRequestParameterHelper = apiRequestParameterHelper;
 		this.dataValidator = dataValidator;
+		this.tptBeneficiaryReadPlatformService = tptBeneficiaryReadPlatformService;
+		this.configurationDomainService = configurationDomainService;
+		this.accountTransfersReadPlatformService = accountTransfersReadPlatformService;
 	}
 
 	@GET
 	@Path("template")
 	@Consumes({ MediaType.APPLICATION_JSON })
 	@Produces({ MediaType.APPLICATION_JSON })
-	public String template(@Context final UriInfo uriInfo) {
+	public String template(
+			@DefaultValue("") @QueryParam("type") final String type,
+			@Context final UriInfo uriInfo) {
 
 		AppUser user = this.context.authenticatedUser();
-		Collection<SelfAccountTemplateData> templateData = this.selfAccountTransferReadService
-				.retrieveSelfAccountTemplateData(user);
-
 		final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper
 				.process(uriInfo.getQueryParameters());
-		return this.toApiJsonSerializer.serialize(settings,
-				new SelfAccountTransferData(templateData));
+		Collection<SelfAccountTemplateData> selfTemplateData = this.selfAccountTransferReadService
+				.retrieveSelfAccountTemplateData(user);
+
+		if (type.equals("tpt")) {
+			Collection<SelfAccountTemplateData> tptTemplateData = this.tptBeneficiaryReadPlatformService
+					.retrieveTPTSelfAccountTemplateData(user);
+			return this.toApiJsonSerializer.serialize(settings,
+					new SelfAccountTransferData(selfTemplateData,
+							tptTemplateData));
+		}
+
+		return this.toApiJsonSerializer
+				.serialize(settings, new SelfAccountTransferData(
+						selfTemplateData, selfTemplateData));
 	}
 
 	@POST
 	@Consumes({ MediaType.APPLICATION_JSON })
 	@Produces({ MediaType.APPLICATION_JSON })
-	public String create(final String apiRequestBodyAsJson) {
-		this.dataValidator.validateCreate(apiRequestBodyAsJson);
+	public String create(
+			@DefaultValue("") @QueryParam("type") final String type,
+			final String apiRequestBodyAsJson) {
+		Map<String, Object> params = this.dataValidator.validateCreate(type,
+				apiRequestBodyAsJson);
+		if (type.equals("tpt")) {
+			checkForLimits(params);
+		}
 		return this.accountTransfersApiResource.create(apiRequestBodyAsJson);
 	}
 
+	private void checkForLimits(Map<String, Object> params) {
+		SelfAccountTemplateData fromAccount = (SelfAccountTemplateData) params
+				.get("fromAccount");
+		SelfAccountTemplateData toAccount = (SelfAccountTemplateData) params
+				.get("toAccount");
+		LocalDate transactionDate = (LocalDate) params.get("transactionDate");
+		BigDecimal transactionAmount = (BigDecimal) params
+				.get("transactionAmount");
+
+		AppUser user = this.context.authenticatedUser();
+		Long transferLimit = this.tptBeneficiaryReadPlatformService
+				.getTransferLimit(user.getId(), toAccount.getAccountId(),
+						toAccount.getAccountType());
+		if (transferLimit != null && transferLimit > 0) {
+			if (transactionAmount.compareTo(new BigDecimal(transferLimit)) > 0) {
+				throw new BeneficiaryTransferLimitExceededException();
+			}
+		}
+
+		if (this.configurationDomainService.isDailyTPTLimitEnabled()) {
+			Long dailyTPTLimit = this.configurationDomainService
+					.getDailyTPTLimit();
+			if (dailyTPTLimit != null && dailyTPTLimit > 0) {
+				BigDecimal dailyTPTLimitBD = new BigDecimal(dailyTPTLimit);
+				BigDecimal totTransactionAmount = this.accountTransfersReadPlatformService
+						.getTotalTransactionAmount(fromAccount.getAccountId(),
+								fromAccount.getAccountType(), transactionDate);
+				if (totTransactionAmount != null
+						&& totTransactionAmount.compareTo(BigDecimal.ZERO) > 0) {
+					if (dailyTPTLimitBD.compareTo(totTransactionAmount) <= 0
+							|| dailyTPTLimitBD.compareTo(totTransactionAmount
+									.add(transactionAmount)) < 0) {
+						throw new DailyTPTTransactionAmountLimitExceededException(
+								fromAccount.getAccountId(),
+								fromAccount.getAccountType());
+					}
+				}
+			}
+		}
+	}
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/2bd3b062/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/api/SelfBeneficiariesTPTApiConstants.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/api/SelfBeneficiariesTPTApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/api/SelfBeneficiariesTPTApiConstants.java
new file mode 100644
index 0000000..affe03b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/api/SelfBeneficiariesTPTApiConstants.java
@@ -0,0 +1,53 @@
+/**
+ * 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.self.account.api;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public interface SelfBeneficiariesTPTApiConstants {
+
+	public static final String BENEFICIARY_ENTITY_NAME = "SSBENEFICIARYTPT";
+	public static final String RESOURCE_NAME = "beneficiary";
+	public static final String LOCALE = "locale";
+	public static final String NAME_PARAM_NAME = "name";
+	public static final String OFFICE_NAME_PARAM_NAME = "officeName";
+	public static final String ACCOUNT_TYPE_PARAM_NAME = "accountType";
+	public static final String ACCOUNT_NUMBER_PARAM_NAME = "accountNumber";
+	public static final String TRANSFER_LIMIT_PARAM_NAME = "transferLimit";
+
+	public static final String ID_PARAM_NAME = "id";
+	public static final String CLIENT_NAME_PARAM_NAME = "clientName";
+	public static final String ACCOUNT_TYPE_OPTIONS_PARAM_NAME = "accountTypeOptions";
+
+	public static final Set<String> CREATE_REQUEST_DATA_PARAMETERS = new HashSet<>(
+			Arrays.asList(LOCALE, NAME_PARAM_NAME, OFFICE_NAME_PARAM_NAME,
+					ACCOUNT_NUMBER_PARAM_NAME, ACCOUNT_TYPE_PARAM_NAME,
+					TRANSFER_LIMIT_PARAM_NAME));
+
+	public static final Set<String> UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<>(
+			Arrays.asList(NAME_PARAM_NAME, TRANSFER_LIMIT_PARAM_NAME));
+
+	public static final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(
+			Arrays.asList(NAME_PARAM_NAME, OFFICE_NAME_PARAM_NAME,
+					ACCOUNT_NUMBER_PARAM_NAME, ACCOUNT_TYPE_PARAM_NAME,
+					TRANSFER_LIMIT_PARAM_NAME, ID_PARAM_NAME,
+					CLIENT_NAME_PARAM_NAME, ACCOUNT_TYPE_OPTIONS_PARAM_NAME));
+}

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/2bd3b062/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/api/SelfBeneficiariesTPTApiResource.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/api/SelfBeneficiariesTPTApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/api/SelfBeneficiariesTPTApiResource.java
new file mode 100644
index 0000000..344d991
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/api/SelfBeneficiariesTPTApiResource.java
@@ -0,0 +1,161 @@
+/**
+ * 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.self.account.api;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.account.PortfolioAccountType;
+import org.apache.fineract.portfolio.account.service.AccountTransferEnumerations;
+import org.apache.fineract.portfolio.self.account.data.SelfBeneficiariesTPTData;
+import org.apache.fineract.portfolio.self.account.service.SelfBeneficiariesTPTReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/self/beneficiaries/tpt")
+@Component
+@Scope("singleton")
+public class SelfBeneficiariesTPTApiResource {
+
+	private final PlatformSecurityContext context;
+	private final DefaultToApiJsonSerializer<SelfBeneficiariesTPTData> toApiJsonSerializer;
+	private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+	private final ApiRequestParameterHelper apiRequestParameterHelper;
+	private final SelfBeneficiariesTPTReadPlatformService readPlatformService;
+
+	@Autowired
+	public SelfBeneficiariesTPTApiResource(
+			final PlatformSecurityContext context,
+			final DefaultToApiJsonSerializer<SelfBeneficiariesTPTData> toApiJsonSerializer,
+			final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
+			final ApiRequestParameterHelper apiRequestParameterHelper,
+			final SelfBeneficiariesTPTReadPlatformService readPlatformService) {
+		this.context = context;
+		this.toApiJsonSerializer = toApiJsonSerializer;
+		this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+		this.apiRequestParameterHelper = apiRequestParameterHelper;
+		this.readPlatformService = readPlatformService;
+	}
+
+	@GET
+	@Path("template")
+	@Consumes({ MediaType.APPLICATION_JSON })
+	@Produces({ MediaType.APPLICATION_JSON })
+	public String template(@Context final UriInfo uriInfo) {
+
+		final EnumOptionData loanAccountType = AccountTransferEnumerations
+				.accountType(PortfolioAccountType.LOAN);
+		final EnumOptionData savingsAccountType = AccountTransferEnumerations
+				.accountType(PortfolioAccountType.SAVINGS);
+
+		final Collection<EnumOptionData> accountTypeOptions = Arrays.asList(
+				savingsAccountType, loanAccountType);
+
+		SelfBeneficiariesTPTData templateData = new SelfBeneficiariesTPTData(
+				accountTypeOptions);
+
+		final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper
+				.process(uriInfo.getQueryParameters());
+		return this.toApiJsonSerializer.serialize(settings, templateData,
+				SelfBeneficiariesTPTApiConstants.RESPONSE_DATA_PARAMETERS);
+	}
+
+	@POST
+	@Consumes({ MediaType.APPLICATION_JSON })
+	@Produces({ MediaType.APPLICATION_JSON })
+	public String add(final String apiRequestBodyAsJson) {
+
+		final CommandWrapper commandRequest = new CommandWrapperBuilder()
+				.addSelfServiceBeneficiaryTPT().withJson(apiRequestBodyAsJson)
+				.build();
+		final CommandProcessingResult result = this.commandsSourceWritePlatformService
+				.logCommandSource(commandRequest);
+		return this.toApiJsonSerializer.serialize(result);
+	}
+
+	@PUT
+	@Path("{beneficiaryId}")
+	@Consumes({ MediaType.APPLICATION_JSON })
+	@Produces({ MediaType.APPLICATION_JSON })
+	public String update(@PathParam("beneficiaryId") final Long beneficiaryId,
+			final String apiRequestBodyAsJson) {
+
+		final CommandWrapper commandRequest = new CommandWrapperBuilder()
+				.updateSelfServiceBeneficiaryTPT(beneficiaryId)
+				.withJson(apiRequestBodyAsJson).build();
+		final CommandProcessingResult result = this.commandsSourceWritePlatformService
+				.logCommandSource(commandRequest);
+		return this.toApiJsonSerializer.serialize(result);
+	}
+
+	@DELETE
+	@Path("{beneficiaryId}")
+	@Consumes({ MediaType.APPLICATION_JSON })
+	@Produces({ MediaType.APPLICATION_JSON })
+	public String delete(@PathParam("beneficiaryId") final Long beneficiaryId,
+			final String apiRequestBodyAsJson) {
+
+		final CommandWrapper commandRequest = new CommandWrapperBuilder()
+				.deleteSelfServiceBeneficiaryTPT(beneficiaryId)
+				.withJson(apiRequestBodyAsJson).build();
+		final CommandProcessingResult result = this.commandsSourceWritePlatformService
+				.logCommandSource(commandRequest);
+		return this.toApiJsonSerializer.serialize(result);
+	}
+
+	@GET
+	@Consumes({ MediaType.APPLICATION_JSON })
+	@Produces({ MediaType.APPLICATION_JSON })
+	public String retrieveAll(@Context final UriInfo uriInfo) {
+
+		this.context.authenticatedUser().validateHasReadPermission(
+				SelfBeneficiariesTPTApiConstants.BENEFICIARY_ENTITY_NAME);
+
+		final Collection<SelfBeneficiariesTPTData> beneficiaries = this.readPlatformService
+				.retrieveAll();
+
+		final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper
+				.process(uriInfo.getQueryParameters());
+		return this.toApiJsonSerializer.serialize(settings, beneficiaries,
+				SelfBeneficiariesTPTApiConstants.RESPONSE_DATA_PARAMETERS);
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/2bd3b062/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfAccountTemplateData.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfAccountTemplateData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfAccountTemplateData.java
index 03e872b..17b2ebe 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfAccountTemplateData.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfAccountTemplateData.java
@@ -103,4 +103,13 @@ public class SelfAccountTemplateData implements
 				.toHashCode();
 	}
 
+	public Long getAccountId() {
+		return this.accountId;
+	}
+
+	public Integer getAccountType() {
+		return this.accountType.getId().intValue();
+	}
+
+	
 }

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/2bd3b062/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfAccountTransferData.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfAccountTransferData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfAccountTransferData.java
index b3d6669..5ca23c2 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfAccountTransferData.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfAccountTransferData.java
@@ -23,11 +23,14 @@ import java.util.Collection;
 @SuppressWarnings("unused")
 public class SelfAccountTransferData {
 
-	private final Collection<SelfAccountTemplateData> accountOptions;
+	private final Collection<SelfAccountTemplateData> fromAccountOptions;
+	private final Collection<SelfAccountTemplateData> toAccountOptions;
 
 	public SelfAccountTransferData(
-			final Collection<SelfAccountTemplateData> accountOptions) {
-		this.accountOptions = accountOptions;
+			final Collection<SelfAccountTemplateData> fromAccountOptions,
+			Collection<SelfAccountTemplateData> toAccountOptions) {
+		this.fromAccountOptions = fromAccountOptions;
+		this.toAccountOptions = toAccountOptions;
 	}
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/2bd3b062/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfAccountTransferDataValidator.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfAccountTransferDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfAccountTransferDataValidator.java
index c78852f..f986a67 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfAccountTransferDataValidator.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfAccountTransferDataValidator.java
@@ -27,10 +27,16 @@ import static org.apache.fineract.portfolio.account.AccountDetailConstants.toAcc
 import static org.apache.fineract.portfolio.account.AccountDetailConstants.toClientIdParamName;
 import static org.apache.fineract.portfolio.account.AccountDetailConstants.toOfficeIdParamName;
 import static org.apache.fineract.portfolio.account.api.AccountTransfersApiConstants.ACCOUNT_TRANSFER_RESOURCE_NAME;
+import static org.apache.fineract.portfolio.account.api.AccountTransfersApiConstants.transferAmountParamName;
+import static org.apache.fineract.portfolio.account.api.AccountTransfersApiConstants.transferDateParamName;
+import static org.apache.fineract.portfolio.account.api.AccountTransfersApiConstants.transferDescriptionParamName;
 
+import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import org.apache.commons.lang.StringUtils;
 import org.apache.fineract.infrastructure.core.data.ApiParameterError;
@@ -40,7 +46,9 @@ import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidati
 import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
 import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
 import org.apache.fineract.portfolio.self.account.service.SelfAccountTransferReadService;
+import org.apache.fineract.portfolio.self.account.service.SelfBeneficiariesTPTReadPlatformService;
 import org.apache.fineract.useradministration.domain.AppUser;
+import org.joda.time.LocalDate;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
@@ -51,19 +59,22 @@ public class SelfAccountTransferDataValidator {
 
 	private final PlatformSecurityContext context;
 	private final SelfAccountTransferReadService selfAccountTransferReadService;
+	private final SelfBeneficiariesTPTReadPlatformService tptBeneficiaryReadPlatformService;
 	private final FromJsonHelper fromApiJsonHelper;
 
 	@Autowired
 	public SelfAccountTransferDataValidator(
 			final PlatformSecurityContext context,
 			final SelfAccountTransferReadService selfAccountTransferReadService,
+			final SelfBeneficiariesTPTReadPlatformService tptBeneficiaryReadPlatformService,
 			final FromJsonHelper fromApiJsonHelper) {
 		this.context = context;
 		this.selfAccountTransferReadService = selfAccountTransferReadService;
+		this.tptBeneficiaryReadPlatformService = tptBeneficiaryReadPlatformService;
 		this.fromApiJsonHelper = fromApiJsonHelper;
 	}
 
-	public void validateCreate(String apiRequestBodyAsJson) {
+	public Map<String,Object> validateCreate(String type, String apiRequestBodyAsJson) {
 		if (StringUtils.isBlank(apiRequestBodyAsJson)) {
 			throw new InvalidJsonException();
 		}
@@ -126,6 +137,16 @@ public class SelfAccountTransferDataValidator {
 							"Cannot transfer from Loan account to another Loan account.");
 		}
 
+        final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed(transferDateParamName, element);
+        baseDataValidator.reset().parameter(transferDateParamName).value(transactionDate).notNull();
+
+        final BigDecimal transactionAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(transferAmountParamName, element);
+        baseDataValidator.reset().parameter(transferAmountParamName).value(transactionAmount).notNull().positiveAmount();
+
+        final String transactionDescription = this.fromApiJsonHelper.extractStringNamed(transferDescriptionParamName, element);
+        baseDataValidator.reset().parameter(transferDescriptionParamName).value(transactionDescription).notBlank()
+                .notExceedingLengthOf(200);
+
 		throwExceptionIfValidationWarningsExist(dataValidationErrors);
 
 		SelfAccountTemplateData fromAccount = new SelfAccountTemplateData(
@@ -133,21 +154,35 @@ public class SelfAccountTransferDataValidator {
 		SelfAccountTemplateData toAccount = new SelfAccountTemplateData(
 				toAccountId, toAccountType, toClientId, toOfficeId);
 
-		validateSelfUserAccounts(fromAccount, toAccount, baseDataValidator);
+		validateUserAccounts(fromAccount, toAccount, baseDataValidator, type);
 		throwExceptionIfValidationWarningsExist(dataValidationErrors);
+		
+		Map<String, Object> ret = new HashMap<>();
+		ret.put("fromAccount", fromAccount);
+		ret.put("toAccount", toAccount);
+		ret.put("transactionDate", transactionDate);
+		ret.put("transactionAmount", transactionAmount);
+		
+		return ret;
 
 	}
 
-	private void validateSelfUserAccounts(
+	private void validateUserAccounts(
 			final SelfAccountTemplateData fromAccount,
 			final SelfAccountTemplateData toAccount,
-			final DataValidatorBuilder baseDataValidator) {
+			final DataValidatorBuilder baseDataValidator, final String type) {
 		AppUser user = this.context.authenticatedUser();
-		Collection<SelfAccountTemplateData> userValidAccounts = this.selfAccountTransferReadService
+		Collection<SelfAccountTemplateData> validFromAccounts = this.selfAccountTransferReadService
 				.retrieveSelfAccountTemplateData(user);
 
+		Collection<SelfAccountTemplateData> validToAccounts = validFromAccounts;
+		if (type.equals("tpt")) {
+			validToAccounts = this.tptBeneficiaryReadPlatformService
+					.retrieveTPTSelfAccountTemplateData(user);
+		}
+
 		boolean validFromAccount = false;
-		for (SelfAccountTemplateData validAccount : userValidAccounts) {
+		for (SelfAccountTemplateData validAccount : validFromAccounts) {
 			if (validAccount.equals(fromAccount)) {
 				validFromAccount = true;
 				break;
@@ -155,7 +190,7 @@ public class SelfAccountTransferDataValidator {
 		}
 
 		boolean validToAccount = false;
-		for (SelfAccountTemplateData validAccount : userValidAccounts) {
+		for (SelfAccountTemplateData validAccount : validToAccounts) {
 			if (validAccount.equals(toAccount)) {
 				validToAccount = true;
 				break;

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/2bd3b062/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfBeneficiariesTPTData.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfBeneficiariesTPTData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfBeneficiariesTPTData.java
new file mode 100644
index 0000000..ff56606
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfBeneficiariesTPTData.java
@@ -0,0 +1,69 @@
+/**
+ * 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.self.account.data;
+
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+
+public class SelfBeneficiariesTPTData {
+	@SuppressWarnings("unused")
+	private final Long id;
+	@SuppressWarnings("unused")
+	private final String name;
+	@SuppressWarnings("unused")
+	private final String officeName;
+	@SuppressWarnings("unused")
+	private final String clientName;
+	@SuppressWarnings("unused")
+	private final EnumOptionData accountType;
+	@SuppressWarnings("unused")
+	private final String accountNumber;
+	@SuppressWarnings("unused")
+	private final Long transferLimit;
+	@SuppressWarnings("unused")
+	private final Collection<EnumOptionData> accountTypeOptions;
+
+	public SelfBeneficiariesTPTData(
+			final Collection<EnumOptionData> accountTypeOptions) {
+		this.accountTypeOptions = accountTypeOptions;
+		this.id = null;
+		this.name = null;
+		this.officeName = null;
+		this.clientName = null;
+		this.accountType = null;
+		this.accountNumber = null;
+		this.transferLimit = null;
+	}
+
+	public SelfBeneficiariesTPTData(final Long id, final String name,
+			final String officeName, final String clientName,
+			final EnumOptionData accountType, final String accountNumber,
+			final Long transferLimit) {
+		this.accountTypeOptions = null;
+		this.id = id;
+		this.name = name;
+		this.officeName = officeName;
+		this.clientName = clientName;
+		this.accountType = accountType;
+		this.accountNumber = accountNumber;
+		this.transferLimit = transferLimit;
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/2bd3b062/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfBeneficiariesTPTDataValidator.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfBeneficiariesTPTDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfBeneficiariesTPTDataValidator.java
new file mode 100644
index 0000000..2ba2899
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfBeneficiariesTPTDataValidator.java
@@ -0,0 +1,165 @@
+/**
+ * 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.self.account.data;
+
+import static org.apache.fineract.portfolio.self.account.api.SelfBeneficiariesTPTApiConstants.RESOURCE_NAME;
+import static org.apache.fineract.portfolio.self.account.api.SelfBeneficiariesTPTApiConstants.NAME_PARAM_NAME;
+import static org.apache.fineract.portfolio.self.account.api.SelfBeneficiariesTPTApiConstants.OFFICE_NAME_PARAM_NAME;
+import static org.apache.fineract.portfolio.self.account.api.SelfBeneficiariesTPTApiConstants.ACCOUNT_TYPE_PARAM_NAME;
+import static org.apache.fineract.portfolio.self.account.api.SelfBeneficiariesTPTApiConstants.ACCOUNT_NUMBER_PARAM_NAME;
+import static org.apache.fineract.portfolio.self.account.api.SelfBeneficiariesTPTApiConstants.TRANSFER_LIMIT_PARAM_NAME;
+import static org.apache.fineract.portfolio.self.account.api.SelfBeneficiariesTPTApiConstants.CREATE_REQUEST_DATA_PARAMETERS;
+import static org.apache.fineract.portfolio.self.account.api.SelfBeneficiariesTPTApiConstants.UPDATE_REQUEST_DATA_PARAMETERS;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.portfolio.account.PortfolioAccountType;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+
+@Component
+public class SelfBeneficiariesTPTDataValidator {
+
+	private final FromJsonHelper fromApiJsonHelper;
+
+	@Autowired
+	public SelfBeneficiariesTPTDataValidator(
+			final FromJsonHelper fromApiJsonHelper) {
+		this.fromApiJsonHelper = fromApiJsonHelper;
+	}
+
+	public HashMap<String, Object> validateForCreate(String json) {
+		if (StringUtils.isBlank(json)) {
+			throw new InvalidJsonException();
+		}
+
+		final Type typeOfMap = new TypeToken<Map<String, Object>>() {
+		}.getType();
+		this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
+				CREATE_REQUEST_DATA_PARAMETERS);
+
+		final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+		final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(
+				dataValidationErrors).resource(RESOURCE_NAME);
+		final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+		final String name = this.fromApiJsonHelper.extractStringNamed(
+				NAME_PARAM_NAME, element);
+		baseDataValidator.reset().parameter(NAME_PARAM_NAME).value(name)
+				.notBlank().notExceedingLengthOf(50);
+
+		final String officeName = this.fromApiJsonHelper.extractStringNamed(
+				OFFICE_NAME_PARAM_NAME, element);
+		baseDataValidator.reset().parameter(OFFICE_NAME_PARAM_NAME)
+				.value(officeName).notBlank()
+				.notExceedingLengthOf(50);
+
+		final String accountNo = this.fromApiJsonHelper.extractStringNamed(
+				ACCOUNT_NUMBER_PARAM_NAME, element);
+		baseDataValidator.reset().parameter(ACCOUNT_NUMBER_PARAM_NAME)
+				.value(accountNo).notBlank().notExceedingLengthOf(20);
+
+		final Integer accountType = this.fromApiJsonHelper.extractIntegerNamed(
+				ACCOUNT_TYPE_PARAM_NAME, element, this.fromApiJsonHelper
+						.extractLocaleParameter(element.getAsJsonObject()));
+		baseDataValidator
+				.reset()
+				.parameter(ACCOUNT_TYPE_PARAM_NAME)
+				.value(accountType)
+				.notNull()
+				.isOneOfTheseValues(PortfolioAccountType.LOAN.getValue(),
+						PortfolioAccountType.SAVINGS.getValue());
+
+		final Long transferLimit = this.fromApiJsonHelper.extractLongNamed(
+				TRANSFER_LIMIT_PARAM_NAME, element);
+		baseDataValidator.reset().parameter(TRANSFER_LIMIT_PARAM_NAME)
+				.value(transferLimit).ignoreIfNull().longGreaterThanZero();
+
+		throwExceptionIfValidationWarningsExist(dataValidationErrors);
+
+		HashMap<String, Object> ret = new HashMap<>();
+		ret.put(NAME_PARAM_NAME, name);
+		ret.put(OFFICE_NAME_PARAM_NAME, officeName);
+		ret.put(ACCOUNT_NUMBER_PARAM_NAME, accountNo);
+		ret.put(ACCOUNT_TYPE_PARAM_NAME, accountType);
+		ret.put(TRANSFER_LIMIT_PARAM_NAME, transferLimit);
+
+		return ret;
+	}
+
+	public HashMap<String, Object> validateForUpdate(String json) {
+		if (StringUtils.isBlank(json)) {
+			throw new InvalidJsonException();
+		}
+
+		final Type typeOfMap = new TypeToken<Map<String, Object>>() {
+		}.getType();
+		this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
+				UPDATE_REQUEST_DATA_PARAMETERS);
+
+		final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+		final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(
+				dataValidationErrors).resource(RESOURCE_NAME);
+		final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+		HashMap<String, Object> ret = new HashMap<>();
+
+		if (this.fromApiJsonHelper.parameterExists(NAME_PARAM_NAME, element)) {
+			final String name = this.fromApiJsonHelper.extractStringNamed(
+					NAME_PARAM_NAME, element);
+			baseDataValidator.reset().parameter(NAME_PARAM_NAME).value(name)
+					.notBlank().notExceedingLengthOf(50);
+			ret.put(NAME_PARAM_NAME, name);
+		}
+
+		if (this.fromApiJsonHelper.parameterExists(TRANSFER_LIMIT_PARAM_NAME,
+				element)) {
+			final Long transferLimit = this.fromApiJsonHelper.extractLongNamed(
+					TRANSFER_LIMIT_PARAM_NAME, element);
+			baseDataValidator.reset().parameter(TRANSFER_LIMIT_PARAM_NAME)
+					.value(transferLimit).ignoreIfNull().longGreaterThanZero();
+			ret.put(TRANSFER_LIMIT_PARAM_NAME, transferLimit);
+		}
+
+		throwExceptionIfValidationWarningsExist(dataValidationErrors);
+
+		return ret;
+	}
+
+	private void throwExceptionIfValidationWarningsExist(
+			final List<ApiParameterError> dataValidationErrors) {
+		if (!dataValidationErrors.isEmpty()) {
+			throw new PlatformApiDataValidationException(dataValidationErrors);
+		}
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/2bd3b062/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/domain/SelfBeneficiariesTPT.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/domain/SelfBeneficiariesTPT.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/domain/SelfBeneficiariesTPT.java
new file mode 100644
index 0000000..76d0432
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/domain/SelfBeneficiariesTPT.java
@@ -0,0 +1,136 @@
+/**
+ * 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.self.account.domain;
+
+import static org.apache.fineract.portfolio.self.account.api.SelfBeneficiariesTPTApiConstants.NAME_PARAM_NAME;
+import static org.apache.fineract.portfolio.self.account.api.SelfBeneficiariesTPTApiConstants.TRANSFER_LIMIT_PARAM_NAME;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+
+import org.springframework.data.jpa.domain.AbstractPersistable;
+
+@Entity
+@Table(name = "m_selfservice_beneficiaries_tpt", uniqueConstraints = { @UniqueConstraint(columnNames = {
+		"name", "app_user_id", "is_active" }, name = "name") })
+public class SelfBeneficiariesTPT extends AbstractPersistable<Long> {
+
+	@Column(name = "app_user_id", nullable = false)
+	private Long appUserId;
+
+	@Column(name = "name", length = 50, nullable = false)
+	private String name;
+
+	@Column(name = "office_id", nullable = false)
+	private Long officeId;
+
+	@Column(name = "client_id", nullable = false)
+	private Long clientId;
+
+	@Column(name = "account_id", nullable = false)
+	private Long accountId;
+
+	@Column(name = "account_type", nullable = false)
+	private Integer accountType;
+
+	@Column(name = "transfer_limit", nullable = true)
+	private Long transferLimit;
+
+	@Column(name = "is_active", nullable = false)
+	private boolean isActive = true;
+
+	protected SelfBeneficiariesTPT() {
+		//
+	}
+
+	public SelfBeneficiariesTPT(Long appUserId, String name, Long officeId,
+			Long clientId, Long accountId, Integer accountType,
+			Long transferLimit) {
+		this.appUserId = appUserId;
+		this.name = name;
+		this.officeId = officeId;
+		this.clientId = clientId;
+		this.accountId = accountId;
+		this.accountType = accountType;
+		this.transferLimit = transferLimit;
+	}
+
+	public String getName() {
+		return this.name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public Long getTransferLimit() {
+		return this.transferLimit;
+	}
+
+	public void setTransferLimit(Long transferLimit) {
+		this.transferLimit = transferLimit;
+	}
+
+	public boolean isActive() {
+		return this.isActive;
+	}
+
+	public void setActive(boolean isActive) {
+		this.isActive = isActive;
+	}
+
+	public Long getAppUserId() {
+		return this.appUserId;
+	}
+
+	public Long getOfficeId() {
+		return this.officeId;
+	}
+
+	public Long getClientId() {
+		return this.clientId;
+	}
+
+	public Long getAccountId() {
+		return this.accountId;
+	}
+
+	public Integer getAccountType() {
+		return this.accountType;
+	}
+
+	public Map<String, Object> update(String newName, Long newTransferLimit) {
+		Map<String, Object> changes = new HashMap<>();
+		if (!this.name.equals(newName)) {
+			this.name = newName;
+			changes.put(NAME_PARAM_NAME, newName);
+		}
+		if ((this.transferLimit !=null && !this.transferLimit.equals(newTransferLimit))
+				|| (this.transferLimit == null && newTransferLimit != null)) {
+			this.transferLimit = newTransferLimit;
+			changes.put(TRANSFER_LIMIT_PARAM_NAME, newTransferLimit);
+		}
+		return changes;
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/2bd3b062/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/domain/SelfBeneficiariesTPTRepository.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/domain/SelfBeneficiariesTPTRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/domain/SelfBeneficiariesTPTRepository.java
new file mode 100644
index 0000000..a74ebea
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/domain/SelfBeneficiariesTPTRepository.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.portfolio.self.account.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface SelfBeneficiariesTPTRepository extends
+		JpaRepository<SelfBeneficiariesTPT, Long>,
+		JpaSpecificationExecutor<SelfBeneficiariesTPT> {
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/2bd3b062/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/exception/BeneficiaryTransferLimitExceededException.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/exception/BeneficiaryTransferLimitExceededException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/exception/BeneficiaryTransferLimitExceededException.java
new file mode 100644
index 0000000..3623d33
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/exception/BeneficiaryTransferLimitExceededException.java
@@ -0,0 +1,30 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.self.account.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class BeneficiaryTransferLimitExceededException extends AbstractPlatformDomainRuleException {
+
+	public BeneficiaryTransferLimitExceededException() {
+		super("error.msg.beneficiary.transfer.amount.limit.for.beneficiary.exceeded",
+				"Transfer amount limit for beneficiary exceeded");
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/2bd3b062/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/exception/DailyTPTTransactionAmountLimitExceededException.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/exception/DailyTPTTransactionAmountLimitExceededException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/exception/DailyTPTTransactionAmountLimitExceededException.java
new file mode 100644
index 0000000..fb365f8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/exception/DailyTPTTransactionAmountLimitExceededException.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.self.account.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class DailyTPTTransactionAmountLimitExceededException extends AbstractPlatformDomainRuleException {
+
+	public DailyTPTTransactionAmountLimitExceededException(Long accountId,
+			Integer accountType) {
+		super("error.msg.beneficiary.daily.tpt.transfer.limit.for.fromaccountid."+accountId+".fromaccounttype."+accountType+".exceeded",
+				"Daily third party transfer limit for the source account excceeded");
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/2bd3b062/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/exception/InvalidAccountInformationException.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/exception/InvalidAccountInformationException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/exception/InvalidAccountInformationException.java
new file mode 100644
index 0000000..7362077
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/exception/InvalidAccountInformationException.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.self.account.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class InvalidAccountInformationException extends
+		AbstractPlatformDomainRuleException {
+	public InvalidAccountInformationException(final String officeName,
+			final String accountNumber, final String accountType) {
+		super("error.msg.beneficiary.invalid.account.details.with.officeName."
+				+ officeName + ".accountNumber." + accountNumber
+				+ ".accountType." + accountType,
+				"Invalid Office Name, Account Number, Account Type combination");
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/2bd3b062/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/exception/InvalidBeneficiaryException.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/exception/InvalidBeneficiaryException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/exception/InvalidBeneficiaryException.java
new file mode 100644
index 0000000..daac892
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/exception/InvalidBeneficiaryException.java
@@ -0,0 +1,29 @@
+/**
+ * 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.self.account.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class InvalidBeneficiaryException extends
+		AbstractPlatformDomainRuleException {
+	public InvalidBeneficiaryException(final Long beneficiaryId) {
+		super("error.msg.beneficiary.invalid.beneficiary.id." + beneficiaryId,
+				"Beneficiary ID doesn't belong to the User");
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/2bd3b062/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/handler/AddSelfBeneficiariesTPTCommandHandler.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/handler/AddSelfBeneficiariesTPTCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/handler/AddSelfBeneficiariesTPTCommandHandler.java
new file mode 100644
index 0000000..cd88fa1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/handler/AddSelfBeneficiariesTPTCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.self.account.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.self.account.service.SelfBeneficiariesTPTWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+@CommandType(entity = "SSBENEFICIARYTPT", action = "CREATE")
+public class AddSelfBeneficiariesTPTCommandHandler implements
+		NewCommandSourceHandler {
+	private final SelfBeneficiariesTPTWritePlatformService writePlatformService;
+
+	@Autowired
+	public AddSelfBeneficiariesTPTCommandHandler(
+			final SelfBeneficiariesTPTWritePlatformService writePlatformService) {
+		this.writePlatformService = writePlatformService;
+	}
+
+	@Override
+	public CommandProcessingResult processCommand(final JsonCommand command) {
+		return this.writePlatformService.add(command);
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/2bd3b062/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/handler/DeleteSelfBeneficiariesTPTCommandHandler.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/handler/DeleteSelfBeneficiariesTPTCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/handler/DeleteSelfBeneficiariesTPTCommandHandler.java
new file mode 100644
index 0000000..46f3780
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/handler/DeleteSelfBeneficiariesTPTCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.self.account.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.self.account.service.SelfBeneficiariesTPTWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+@CommandType(entity = "SSBENEFICIARYTPT", action = "DELETE")
+public class DeleteSelfBeneficiariesTPTCommandHandler implements
+		NewCommandSourceHandler {
+	private final SelfBeneficiariesTPTWritePlatformService writePlatformService;
+
+	@Autowired
+	public DeleteSelfBeneficiariesTPTCommandHandler(
+			final SelfBeneficiariesTPTWritePlatformService writePlatformService) {
+		this.writePlatformService = writePlatformService;
+	}
+
+	@Override
+	public CommandProcessingResult processCommand(final JsonCommand command) {
+		return this.writePlatformService.delete(command);
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/2bd3b062/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/handler/UpdateSelfBeneficiariesTPTCommandHandler.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/handler/UpdateSelfBeneficiariesTPTCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/handler/UpdateSelfBeneficiariesTPTCommandHandler.java
new file mode 100644
index 0000000..e92123e
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/handler/UpdateSelfBeneficiariesTPTCommandHandler.java
@@ -0,0 +1,46 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.self.account.handler;
+
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.self.account.service.SelfBeneficiariesTPTWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+@CommandType(entity = "SSBENEFICIARYTPT", action = "UPDATE")
+public class UpdateSelfBeneficiariesTPTCommandHandler implements
+		NewCommandSourceHandler {
+	private final SelfBeneficiariesTPTWritePlatformService writePlatformService;
+
+	@Autowired
+	public UpdateSelfBeneficiariesTPTCommandHandler(
+			final SelfBeneficiariesTPTWritePlatformService writePlatformService) {
+		this.writePlatformService = writePlatformService;
+	}
+
+	@Override
+	public CommandProcessingResult processCommand(final JsonCommand command) {
+		return this.writePlatformService.update(command);
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/2bd3b062/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfBeneficiariesTPTReadPlatformService.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfBeneficiariesTPTReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfBeneficiariesTPTReadPlatformService.java
new file mode 100644
index 0000000..d60b47f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfBeneficiariesTPTReadPlatformService.java
@@ -0,0 +1,36 @@
+/**
+ * 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.self.account.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.portfolio.self.account.data.SelfAccountTemplateData;
+import org.apache.fineract.portfolio.self.account.data.SelfBeneficiariesTPTData;
+import org.apache.fineract.useradministration.domain.AppUser;
+
+public interface SelfBeneficiariesTPTReadPlatformService {
+
+	Collection<SelfBeneficiariesTPTData> retrieveAll();
+
+	Collection<SelfAccountTemplateData> retrieveTPTSelfAccountTemplateData(
+			AppUser user);
+
+	Long getTransferLimit(Long id, Long accountId, Integer accountType);
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/2bd3b062/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfBeneficiariesTPTReadPlatformServiceImpl.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfBeneficiariesTPTReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfBeneficiariesTPTReadPlatformServiceImpl.java
new file mode 100644
index 0000000..c209a4d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfBeneficiariesTPTReadPlatformServiceImpl.java
@@ -0,0 +1,222 @@
+/**
+ * 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.self.account.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.account.PortfolioAccountType;
+import org.apache.fineract.portfolio.account.service.AccountTransferEnumerations;
+import org.apache.fineract.portfolio.self.account.data.SelfAccountTemplateData;
+import org.apache.fineract.portfolio.self.account.data.SelfBeneficiariesTPTData;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+@Service
+public class SelfBeneficiariesTPTReadPlatformServiceImpl implements
+		SelfBeneficiariesTPTReadPlatformService {
+
+	private final PlatformSecurityContext context;
+	private final JdbcTemplate jdbcTemplate;
+	private final BeneficiaryMapper mapper;
+	private final AccountTemplateMapper accountTemplateMapper;
+
+	@Autowired
+	public SelfBeneficiariesTPTReadPlatformServiceImpl(
+			final PlatformSecurityContext context,
+			final RoutingDataSource dataSource) {
+		this.context = context;
+		this.jdbcTemplate = new JdbcTemplate(dataSource);
+		this.mapper = new BeneficiaryMapper();
+		this.accountTemplateMapper = new AccountTemplateMapper();
+	}
+
+	@Override
+	public Collection<SelfBeneficiariesTPTData> retrieveAll() {
+		AppUser user = this.context.authenticatedUser();
+		return this.jdbcTemplate.query(this.mapper.schema(), this.mapper,
+				new Object[] { user.getId(), user.getId() });
+	}
+
+	@Override
+	public Collection<SelfAccountTemplateData> retrieveTPTSelfAccountTemplateData(
+			AppUser user) {
+		return this.jdbcTemplate.query(this.accountTemplateMapper.schema(),
+				this.accountTemplateMapper,
+				new Object[] { user.getId(), user.getId() });
+	}
+
+	private static final class BeneficiaryMapper implements
+			RowMapper<SelfBeneficiariesTPTData> {
+
+		private final String schemaSql;
+
+		public BeneficiaryMapper() {
+			final StringBuilder sqlBuilder = new StringBuilder(
+					"(select b.id as id, ");
+			sqlBuilder.append(" b.name as name, ");
+			sqlBuilder.append(" o.name as officeName, ");
+			sqlBuilder.append(" c.display_name as clientName, ");
+			sqlBuilder.append(" b.account_type as accountType, ");
+			sqlBuilder.append(" s.account_no as accountNumber, ");
+			sqlBuilder.append(" b.transfer_limit as transferLimit ");
+			sqlBuilder.append(" from m_selfservice_beneficiaries_tpt as b ");
+			sqlBuilder
+					.append(" inner join m_office as o on b.office_id = o.id ");
+			sqlBuilder
+					.append(" inner join m_client as c on b.client_id = c.id ");
+			sqlBuilder
+					.append(" inner join m_savings_account as s on b.account_id = s.id ");
+			sqlBuilder.append(" where b.is_active = 1 ");
+			sqlBuilder.append(" and b.account_type = 2 ");
+			sqlBuilder.append(" and b.app_user_id = ?) ");
+			sqlBuilder.append(" union all ");
+			sqlBuilder.append(" (select b.id as id, ");
+			sqlBuilder.append(" b.name as name, ");
+			sqlBuilder.append(" o.name as officeName, ");
+			sqlBuilder.append(" c.display_name as clientName, ");
+			sqlBuilder.append(" b.account_type as accountType, ");
+			sqlBuilder.append(" l.account_no as accountNumber, ");
+			sqlBuilder.append(" b.transfer_limit as transferLimit ");
+			sqlBuilder.append(" from m_selfservice_beneficiaries_tpt as b ");
+			sqlBuilder
+					.append(" inner join m_office as o on b.office_id = o.id ");
+			sqlBuilder
+					.append(" inner join m_client as c on b.client_id = c.id ");
+			sqlBuilder
+					.append(" inner join m_loan as l on b.account_id = l.id ");
+			sqlBuilder.append(" where b.is_active = 1 ");
+			sqlBuilder.append(" and b.account_type = 1 ");
+			sqlBuilder.append(" and b.app_user_id = ?) ");
+
+			this.schemaSql = sqlBuilder.toString();
+		}
+
+		public String schema() {
+			return this.schemaSql;
+		}
+
+		@Override
+		public SelfBeneficiariesTPTData mapRow(final ResultSet rs,
+				@SuppressWarnings("unused") final int rowNum)
+				throws SQLException {
+
+			final Long id = rs.getLong("id");
+			final String name = rs.getString("name");
+			final String officeName = rs.getString("officeName");
+			final String clientName = rs.getString("clientName");
+			final Integer accountTypeId = rs.getInt("accountType");
+			final EnumOptionData accountType = AccountTransferEnumerations
+					.accountType(PortfolioAccountType.fromInt(accountTypeId));
+			final String accountNumber = rs.getString("accountNumber");
+			final Long transferLimit = rs.getLong("transferLimit");
+
+			return new SelfBeneficiariesTPTData(id, name, officeName,
+					clientName, accountType, accountNumber, transferLimit);
+		}
+	}
+
+	private static final class AccountTemplateMapper implements
+			RowMapper<SelfAccountTemplateData> {
+
+		private final String schemaSql;
+
+		public AccountTemplateMapper() {
+			final StringBuilder sqlBuilder = new StringBuilder(
+					"(select o.name as officeName, ");
+			sqlBuilder.append(" o.id as officeId, ");
+			sqlBuilder.append(" c.display_name as clientName, ");
+			sqlBuilder.append(" c.id as clientId, ");
+			sqlBuilder.append(" b.account_type as accountType, ");
+			sqlBuilder.append(" s.account_no as accountNumber, ");
+			sqlBuilder.append(" s.id as accountId ");
+			sqlBuilder.append(" from m_selfservice_beneficiaries_tpt as b ");
+			sqlBuilder
+					.append(" inner join m_office as o on b.office_id = o.id ");
+			sqlBuilder
+					.append(" inner join m_client as c on b.client_id = c.id ");
+			sqlBuilder
+					.append(" inner join m_savings_account as s on b.account_id = s.id ");
+			sqlBuilder.append(" where b.is_active = 1 ");
+			sqlBuilder.append(" and b.account_type = 2 ");
+			sqlBuilder.append(" and b.app_user_id = ?) ");
+			sqlBuilder.append(" union all ");
+			sqlBuilder.append(" (select o.name as officeName, ");
+			sqlBuilder.append(" o.id as officeId, ");
+			sqlBuilder.append(" c.display_name as clientName, ");
+			sqlBuilder.append(" c.id as clientId, ");
+			sqlBuilder.append(" b.account_type as accountType, ");
+			sqlBuilder.append(" l.account_no as accountNumber, ");
+			sqlBuilder.append(" l.id as accountId ");
+			sqlBuilder.append(" from m_selfservice_beneficiaries_tpt as b ");
+			sqlBuilder
+					.append(" inner join m_office as o on b.office_id = o.id ");
+			sqlBuilder
+					.append(" inner join m_client as c on b.client_id = c.id ");
+			sqlBuilder
+					.append(" inner join m_loan as l on b.account_id = l.id ");
+			sqlBuilder.append(" where b.is_active = 1 ");
+			sqlBuilder.append(" and b.account_type = 1 ");
+			sqlBuilder.append(" and b.app_user_id = ?) ");
+
+			this.schemaSql = sqlBuilder.toString();
+		}
+
+		public String schema() {
+			return this.schemaSql;
+		}
+
+		@Override
+		public SelfAccountTemplateData mapRow(final ResultSet rs,
+				@SuppressWarnings("unused") final int rowNum)
+				throws SQLException {
+
+			final String officeName = rs.getString("officeName");
+			final Long officeId = rs.getLong("officeId");
+			final String clientName = rs.getString("clientName");
+			final Long clientId = rs.getLong("clientId");
+			final Integer accountTypeId = rs.getInt("accountType");
+			final String accountNumber = rs.getString("accountNumber");
+			final Long accountId = rs.getLong("accountId");
+
+			return new SelfAccountTemplateData(accountId, accountNumber,
+					accountTypeId, clientId, clientName, officeId, officeName);
+		}
+	}
+
+	@Override
+	public Long getTransferLimit(Long appUserId, Long accountId, Integer accountType) {
+		final StringBuilder sqlBuilder = new StringBuilder("select b.transfer_limit ");
+		sqlBuilder.append(" from m_selfservice_beneficiaries_tpt as b ");
+		sqlBuilder.append(" where b.app_user_id = ? ");
+		sqlBuilder.append(" and b.account_id = ? ");
+		sqlBuilder.append(" and b.account_type = ? ");
+		sqlBuilder.append(" and b.is_active = 1; ");
+		
+		return this.jdbcTemplate.queryForObject(sqlBuilder.toString(), 
+				new Object[]{appUserId, accountId, accountType}, Long.class);
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/2bd3b062/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfBeneficiariesTPTWritePlatformService.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfBeneficiariesTPTWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfBeneficiariesTPTWritePlatformService.java
new file mode 100644
index 0000000..5d4519b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfBeneficiariesTPTWritePlatformService.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.self.account.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface SelfBeneficiariesTPTWritePlatformService {
+
+	CommandProcessingResult add(JsonCommand command);
+
+	CommandProcessingResult update(JsonCommand command);
+
+	CommandProcessingResult delete(JsonCommand command);
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/2bd3b062/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfBeneficiariesTPTWritePlatformServiceImpl.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfBeneficiariesTPTWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfBeneficiariesTPTWritePlatformServiceImpl.java
new file mode 100644
index 0000000..9a4e8fd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfBeneficiariesTPTWritePlatformServiceImpl.java
@@ -0,0 +1,212 @@
+/**
+ * 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.self.account.service;
+
+import static org.apache.fineract.portfolio.self.account.api.SelfBeneficiariesTPTApiConstants.ACCOUNT_NUMBER_PARAM_NAME;
+import static org.apache.fineract.portfolio.self.account.api.SelfBeneficiariesTPTApiConstants.ACCOUNT_TYPE_PARAM_NAME;
+import static org.apache.fineract.portfolio.self.account.api.SelfBeneficiariesTPTApiConstants.NAME_PARAM_NAME;
+import static org.apache.fineract.portfolio.self.account.api.SelfBeneficiariesTPTApiConstants.OFFICE_NAME_PARAM_NAME;
+import static org.apache.fineract.portfolio.self.account.api.SelfBeneficiariesTPTApiConstants.TRANSFER_LIMIT_PARAM_NAME;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.apache.fineract.portfolio.account.PortfolioAccountType;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepository;
+import org.apache.fineract.portfolio.self.account.data.SelfBeneficiariesTPTDataValidator;
+import org.apache.fineract.portfolio.self.account.domain.SelfBeneficiariesTPT;
+import org.apache.fineract.portfolio.self.account.domain.SelfBeneficiariesTPTRepository;
+import org.apache.fineract.portfolio.self.account.exception.InvalidAccountInformationException;
+import org.apache.fineract.portfolio.self.account.exception.InvalidBeneficiaryException;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataAccessException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class SelfBeneficiariesTPTWritePlatformServiceImpl implements
+		SelfBeneficiariesTPTWritePlatformService {
+
+	private final Logger logger;
+	private final PlatformSecurityContext context;
+	private final SelfBeneficiariesTPTRepository repository;
+	private final SelfBeneficiariesTPTDataValidator validator;
+	private final LoanRepository loanRepo;
+	private final SavingsAccountRepository savingRepo;
+
+	@Autowired
+	public SelfBeneficiariesTPTWritePlatformServiceImpl(
+			final PlatformSecurityContext context,
+			final SelfBeneficiariesTPTRepository repository,
+			final SelfBeneficiariesTPTDataValidator validator,
+			final LoanRepository loanRepo,
+			final SavingsAccountRepository savingRepo) {
+		this.context = context;
+		this.repository = repository;
+		this.validator = validator;
+		this.loanRepo = loanRepo;
+		this.savingRepo = savingRepo;
+		this.logger = LoggerFactory
+				.getLogger(SelfBeneficiariesTPTWritePlatformServiceImpl.class);
+	}
+
+	@Transactional
+	@Override
+	public CommandProcessingResult add(JsonCommand command) {
+		HashMap<String, Object> params = this.validator
+				.validateForCreate(command.json());
+
+		String name = (String) params.get(NAME_PARAM_NAME);
+		Integer accountType = (Integer) params.get(ACCOUNT_TYPE_PARAM_NAME);
+		String accountNumber = (String) params.get(ACCOUNT_NUMBER_PARAM_NAME);
+		String officeName = (String) params.get(OFFICE_NAME_PARAM_NAME);
+		Long transferLimit = (Long) params.get(TRANSFER_LIMIT_PARAM_NAME);
+
+		Long accountId = null;
+		Long clientId = null;
+		Long officeId = null;
+
+		boolean validAccountDetails = true;
+		if (accountType.equals(PortfolioAccountType.LOAN)) {
+			Loan loan = this.loanRepo
+					.findNonClosedLoanByAccountNumber(accountNumber);
+			if (loan != null && loan.getClientId() != null
+					&& loan.getOffice().getName().equals(officeName)) {
+				accountId = loan.getId();
+				officeId = loan.getOfficeId();
+				clientId = loan.getClientId();
+			} else {
+				validAccountDetails = false;
+			}
+		} else {
+			SavingsAccount savings = this.savingRepo
+					.findNonClosedAccountByAccountNumber(accountNumber);
+			if (savings != null
+					&& savings.getClient() != null
+					&& savings.getClient().getOffice().getName()
+							.equals(officeName)) {
+				accountId = savings.getId();
+				clientId = savings.getClient().getId();
+				officeId = savings.getClient().getOffice().getId();
+			} else {
+				validAccountDetails = false;
+			}
+		}
+
+		if (validAccountDetails) {
+			try {
+				AppUser user = this.context.authenticatedUser();
+				SelfBeneficiariesTPT beneficiary = new SelfBeneficiariesTPT(
+						user.getId(), name, officeId, clientId, accountId,
+						accountType, transferLimit);
+				this.repository.save(beneficiary);
+				return new CommandProcessingResultBuilder().withEntityId(
+						beneficiary.getId()).build();
+			} catch (DataAccessException dae) {
+				handleDataIntegrityIssues(command, dae);
+			}
+		}
+		throw new InvalidAccountInformationException(officeName, accountNumber,
+				PortfolioAccountType.fromInt(accountType).getCode());
+
+	}
+
+	@Transactional
+	@Override
+	public CommandProcessingResult update(JsonCommand command) {
+		HashMap<String, Object> params = this.validator
+				.validateForUpdate(command.json());
+		AppUser user = this.context.authenticatedUser();
+		Long beneficiaryId = command.entityId();
+		SelfBeneficiariesTPT beneficiary = this.repository
+				.findOne(beneficiaryId);
+		if (beneficiary != null
+				&& beneficiary.getAppUserId().equals(user.getId())) {
+			String name = (String) params.get(NAME_PARAM_NAME);
+			Long transferLimit = (Long) params.get(TRANSFER_LIMIT_PARAM_NAME);
+
+			Map<String, Object> changes = beneficiary.update(name,
+					transferLimit);
+			if (!changes.isEmpty()) {
+				try {
+					this.repository.save(beneficiary);
+
+					return new CommandProcessingResultBuilder() //
+							.withEntityId(beneficiary.getId()) //
+							.with(changes).build();
+				} catch (DataAccessException dae) {
+					handleDataIntegrityIssues(command, dae);
+				}
+
+			}
+		}
+		throw new InvalidBeneficiaryException(beneficiaryId);
+	}
+
+	@Transactional
+	@Override
+	public CommandProcessingResult delete(JsonCommand command) {
+		AppUser user = this.context.authenticatedUser();
+		Long beneficiaryId = command.entityId();
+		SelfBeneficiariesTPT beneficiary = this.repository
+				.findOne(beneficiaryId);
+		if (beneficiary != null
+				&& beneficiary.getAppUserId().equals(user.getId())) {
+
+			beneficiary.setActive(false);
+			this.repository.save(beneficiary);
+
+			return new CommandProcessingResultBuilder() //
+					.withEntityId(beneficiary.getId()) //
+					.build();
+		}
+		throw new InvalidBeneficiaryException(beneficiaryId);
+	}
+
+	private void handleDataIntegrityIssues(final JsonCommand command,
+			final DataAccessException dae) {
+
+		final Throwable realCause = dae.getMostSpecificCause();
+		if (realCause.getMessage().contains("name")) {
+
+			final String name = command
+					.stringValueOfParameterNamed(NAME_PARAM_NAME);
+			throw new PlatformDataIntegrityException(
+					"error.msg.beneficiary.duplicate.name",
+					"Beneficiary with name `" + name + "` already exists",
+					NAME_PARAM_NAME, name);
+		}
+
+		this.logger.error(dae.getMessage(), dae);
+		throw new PlatformDataIntegrityException(
+				"error.msg.beneficiary.unknown.data.integrity.issue",
+				"Unknown data integrity issue with resource.");
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/2bd3b062/fineract-provider/src/main/resources/sql/migrations/core_db/V304__customer_self_service_third_party_transfers.sql
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V304__customer_self_service_third_party_transfers.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V304__customer_self_service_third_party_transfers.sql
new file mode 100644
index 0000000..fc6be77
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V304__customer_self_service_third_party_transfers.sql
@@ -0,0 +1,24 @@
+INSERT INTO `c_configuration` (`name`, `value`, `date_value`, `enabled`, `is_trap_door`, `description`) VALUES ( 'daily-tpt-limit', 0, NULL, 0, 0, 'Daily limit for third party transfers');
+
+CREATE TABLE `m_selfservice_beneficiaries_tpt` (
+	`id` BIGINT NOT NULL AUTO_INCREMENT,
+	`app_user_id` BIGINT NOT NULL,
+	`name` VARCHAR(50) NOT NULL,
+	`office_id` BIGINT NOT NULL,
+	`client_id` BIGINT NOT NULL,
+	`account_id` BIGINT NOT NULL,
+	`account_type` SMALLINT(4) NOT NULL,
+	`transfer_limit` BIGINT NULL DEFAULT 0,
+	`is_active` BIT(1) NOT NULL DEFAULT 0,
+	PRIMARY KEY (`id`),
+	UNIQUE INDEX `name` (`name`, `app_user_id`, `is_active`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB
+;
+
+INSERT INTO `m_permission`(`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES
+('SSBENEFICIARYTPT', 'READ_SSBENEFICIARYTPT', 'SSBENEFICIARYTPT', 'READ', 0),
+('SSBENEFICIARYTPT', 'CREATE_SSBENEFICIARYTPT', 'SSBENEFICIARYTPT', 'CREATE', 0),
+('SSBENEFICIARYTPT', 'UPDATE_SSBENEFICIARYTPT', 'SSBENEFICIARYTPT', 'UPDATE', 0),
+('SSBENEFICIARYTPT', 'DELETE_SSBENEFICIARYTPT', 'SSBENEFICIARYTPT', 'DELETE', 0);


[2/2] incubator-fineract git commit: Customer Self Service : APIs for Third Party Beneficiary management and Third Party Account Transfers

Posted by na...@apache.org.
Customer Self Service : APIs for Third Party Beneficiary management and Third Party Account Transfers


Project: http://git-wip-us.apache.org/repos/asf/incubator-fineract/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-fineract/commit/2bd3b062
Tree: http://git-wip-us.apache.org/repos/asf/incubator-fineract/tree/2bd3b062
Diff: http://git-wip-us.apache.org/repos/asf/incubator-fineract/diff/2bd3b062

Branch: refs/heads/develop
Commit: 2bd3b0625a182a9e4b300f096c45a2585445e556
Parents: 9d4d901
Author: Adi Narayana Raju <ad...@confluxtechnologies.com>
Authored: Wed May 4 13:00:41 2016 +0530
Committer: Adi Narayana Raju <ad...@confluxtechnologies.com>
Committed: Wed May 4 13:01:25 2016 +0530

----------------------------------------------------------------------
 api-docs/apiLive.htm                            |  379 +++++-
 .../commands/service/CommandWrapperBuilder.java |   25 +
 .../domain/ConfigurationDomainService.java      |    4 +
 .../domain/ConfigurationDomainServiceJpa.java   |   16 +
 .../service/TenantDatabaseUpgradeService.java   |    2 +-
 ...arWritePlatformServiceJpaRepositoryImpl.java |    4 +-
 .../AccountTransfersReadPlatformService.java    |    5 +
 ...AccountTransfersReadPlatformServiceImpl.java | 1120 ++++++++++--------
 .../loanaccount/domain/LoanRepository.java      |    6 +-
 .../domain/SavingsAccountRepository.java        |    3 +
 .../api/SelfAccountTransferApiResource.java     |   95 +-
 .../api/SelfBeneficiariesTPTApiConstants.java   |   53 +
 .../api/SelfBeneficiariesTPTApiResource.java    |  161 +++
 .../account/data/SelfAccountTemplateData.java   |    9 +
 .../account/data/SelfAccountTransferData.java   |    9 +-
 .../data/SelfAccountTransferDataValidator.java  |   49 +-
 .../account/data/SelfBeneficiariesTPTData.java  |   69 ++
 .../data/SelfBeneficiariesTPTDataValidator.java |  165 +++
 .../account/domain/SelfBeneficiariesTPT.java    |  136 +++
 .../domain/SelfBeneficiariesTPTRepository.java  |   28 +
 ...neficiaryTransferLimitExceededException.java |   30 +
 ...TransactionAmountLimitExceededException.java |   31 +
 .../InvalidAccountInformationException.java     |   32 +
 .../exception/InvalidBeneficiaryException.java  |   29 +
 .../AddSelfBeneficiariesTPTCommandHandler.java  |   46 +
 ...eleteSelfBeneficiariesTPTCommandHandler.java |   46 +
 ...pdateSelfBeneficiariesTPTCommandHandler.java |   46 +
 ...SelfBeneficiariesTPTReadPlatformService.java |   36 +
 ...BeneficiariesTPTReadPlatformServiceImpl.java |  222 ++++
 ...elfBeneficiariesTPTWritePlatformService.java |   32 +
 ...eneficiariesTPTWritePlatformServiceImpl.java |  212 ++++
 ...tomer_self_service_third_party_transfers.sql |   24 +
 32 files changed, 2620 insertions(+), 504 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/2bd3b062/api-docs/apiLive.htm
----------------------------------------------------------------------
diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm
index c2fa328..3139b1a 100644
--- a/api-docs/apiLive.htm
+++ b/api-docs/apiLive.htm
@@ -3312,6 +3312,38 @@
 								<td></td>
 								<td></td>
 							</tr>
+							<tr>
+								<td><a href="#selfbentemplate">Beneficiary for Third Party Transfer</a></td>
+								<td>/self/beneficiaries/tpt/template</td>
+								<td></td>
+								<td><a href="#selfbentemplate">Beneficiary TPT Template</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>/self/beneficiaries/tpt</td>
+								<td><a href="#selfaddtpt">Add TPT Beneficiary</a></td>
+								<td><a href="#selfgettpt">Get TPT Beneficiary List</a></td>
+								<td><a href="#selfputtpt">Update TPT Beneficiary</a></td>
+								<td><a href="#selfdeletetpt">Delete TPT Beneficiary</a></td>
+							</tr>
+							<tr>
+								<td><a href="#selftpttransfertemplate">Third Party Account Transfers</a></td>
+								<td>self/accounttransfers/template?type="tpt"</td>
+								<td></td>
+								<td><a href="#selftpttransfertemplate">Third Party Account Transfer Template</a></td>
+								<td></td>
+								<td></td>
+							</tr>
+							<tr>
+								<td></td>
+								<td>self/accounttransfers?type="tpt"</td>
+								<td><a href="#selftpttransfer">Third Party Account Transfer</a></td>
+								<td></td>
+								<td></td>
+								<td></td>
+							</tr>
 						</table>
 					</div>
 				</div>
@@ -42193,7 +42225,35 @@ GET https://DomainName/api/v1/self/savingsaccounts/{accountId}/charges/{savingsA
 	        </code>
 			<code class="method-response">
 {
-  "accountOptions": [
+  "fromAccountOptions": [
+	{
+		"accountId": 1,
+		"accountNo": "00000001",
+		"accountType": {
+			"id": 2,
+			"code": "accountType.savings",
+			"value": "Savings Account"
+		},
+		"clientId": 1,
+		"clientName": "ABC",
+		"officeId": 1,
+		"officeName": "HEAD OFFICE"
+	},
+	{
+		"accountId": 5,
+		"accountNo": "00000005",
+		"accountType": {
+			"id": 1,
+			"code": "accountType.loan",
+			"value": "Loan Account"
+		},
+		"clientId": 2,
+		"clientName": "XYZ",
+		"officeId": 3,
+		"officeName": "REGIONAL OFFICE"
+	}
+  ],
+ "toAccountOptions": [
 	{
 		"accountId": 1,
 		"accountNo": "00000001",
@@ -42221,6 +42281,7 @@ GET https://DomainName/api/v1/self/savingsaccounts/{accountId}/charges/{savingsA
 		"officeName": "REGIONAL OFFICE"
 	}
   ]
+
 }
 			</code>
 	    </div>
@@ -42262,6 +42323,322 @@ No Request Body:
 	    </div>
 	</div>
 
+	<a id="selfbentemplate" name="selfbentemplate" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Beneficiary Third Party Transfer Template</h2>
+	        <p>Returns Account Type enumerations. Self User is expected to know office name and account number to be able to add beneficiary.</p>
+	        <p>Example Requests:</p>
+	        <div class=apiClick>/self/beneficiaries/tpt/template</div>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">GET https://DomainName/api/v1/self/beneficiaries/tpt/template
+	        </code>
+			<code class="method-response">
+    {
+        "accountTypeOptions":
+        [
+            {
+                "id": 2,
+                "code": "accountType.savings",
+                "value": "Savings Account"
+            },
+            {
+                "id": 1,
+                "code": "accountType.loan",
+                "value": "Loan Account"
+            }
+        ]
+    }
+			</code>
+	    </div>
+	</div>
+
+	<a id="selfaddtpt" name="selfaddtpt" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Add TPT Beneficiary</h2>
+	        <p>Api to add third party beneficiary linked to current user.</p>
+	        <p>Parameter Definitions</p>
+	        <p>name : Nick name for beneficiary, should be unique for an self service user</p>
+	        <p>officeName : Office Name of beneficiary(not id)</p>
+	        <p>accountNumber : Account Number of beneficiary(not id)</p>
+	        <p>transferLimit : Each transfer initiated to this account will not exceed this amount</p>
+	        <p>Example Requests:</p>
+	        <div class=apiClick>/self/beneficiaries/tpt</div>
+			<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Mandatory Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>name, officeName, accountNumber, accountType</td>
+						</tr>
+					</table>
+					<br/>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Optional Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>transferLimit
+							</td>
+						</tr>
+					</table>
+	    </div>
+		
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://DomainName/api/v1/self/beneficiaries/tpt
+	        </code>
+					<code class="method-request">
+POST self/beneficiaries/tpt
+Content-Type: application/json
+Request Body:
+{
+    "locale": "en_GB",
+	"name": "beneficiary nick name",
+	"officeName": "HEAD OFFICE",
+	"accountNumber": "0000001",
+	"accountType": 1,
+	"transferLimit": 1000
+    
+}
+					</code>
+					<code class="method-response">
+{
+  "resourceId": 5
+}
+			</code>
+	    </div>
+	</div>
+
+	<a id="selfgettpt" name="selfgettpt" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Get All TPT Beneficiary</h2>
+	        <p>Api to get all third party beneficiary linked to current user.</p>
+	        <p>Example Requests:</p>
+	        <div class=apiClick>/self/beneficiaries/tpt</div>
+	    </div>
+		
+	    <div class="method-example">
+	        <code class="method-declaration">GET https://DomainName/api/v1/self/beneficiaries/tpt
+	        </code>
+					<code class="method-request">
+GET self/beneficiaries/tpt
+Content-Type: application/json
+					</code>
+					<code class="method-response">
+{
+[
+  {
+    "id": 1,
+    "name": "Client2Savings",
+    "officeName": "Test Office",
+    "clientName": "FN2 LN2",
+    "accountType": {
+      "id": 2,
+      "code": "accountType.savings",
+      "value": "Savings Account"
+    },
+    "accountNumber": "000000002",
+    "transferLimit": 0
+  },
+  {
+    "id": 4,
+    "name": "Client2Loan",
+    "officeName": "Test Office",
+    "clientName": "FN2 LN2",
+    "accountType": {
+      "id": 1,
+      "code": "accountType.loan",
+      "value": "Loan Account"
+    },
+    "accountNumber": "000000002",
+    "transferLimit": 1000
+  }]
+}
+			</code>
+	    </div>
+	</div>
+
+	<a id="selfputtpt" name="selfputtpt" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Update TPT Beneficiary</h2>
+	        <p>Api to update third party beneficiary linked to current user.</p>
+	        <p>Example Requests:</p>
+	        <div class=apiClick>/self/beneficiaries/tpt/{beneficiaryId}</div>
+					<br/>
+					<table class=matrixHeading>
+						<tr class="matrixHeadingBG">
+							<td><div class="fineractHeading2">Optional Fields</div></td>
+						</tr>
+						<tr class=alt>
+							<td>name, transferLimit
+							</td>
+						</tr>
+					</table>
+	    </div>
+		
+	    <div class="method-example">
+	        <code class="method-declaration">PUT https://DomainName/api/v1/self/beneficiaries/tpt/{beneficiaryId}
+	        </code>
+					<code class="method-request">
+PUT self/beneficiaries/tpt/5
+Content-Type: application/json
+Request Body:
+{
+	"name": "beneficiary nick name",
+	"transferLimit": 1000
+    
+}
+					</code>
+					<code class="method-response">
+{
+  "resourceId": 5,
+  "changes": {
+    "transferLimit": 1000,
+    "name": "Client22"
+  }
+}
+			</code>
+	    </div>
+	</div>
+
+	<a id="selfdeletetpt" name="selfdeletetpt" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Delete TPT Beneficiary</h2>
+	        <p>Api to delete third party beneficiary linked to current user.</p>
+	        <p>Example Requests:</p>
+	        <div class=apiClick>/self/beneficiaries/tpt/{beneficiaryId}</div>
+	    </div>
+		
+	    <div class="method-example">
+	        <code class="method-declaration">DELETE https://DomainName/api/v1/self/beneficiaries/tpt/{beneficiaryId}
+	        </code>
+					<code class="method-request">
+DELETE self/beneficiaries/tpt/5
+Content-Type: application/json
+					</code>
+					<code class="method-response">
+{
+  "resourceId": 5
+}
+			</code>
+	    </div>
+	</div>
+
+	<a id="selftpttransfertemplate" name="selftpttransfertemplate" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Third Party Account Transfer Template</h2>
+	        <p>Returns list of loan/savings accounts that can be used for third party account transfer</p>
+	        <p>Example Requests:</p>
+	        <div class=apiClick>self/accounttransfers/template?type="tpt"</div>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">GET https://DomainName/api/v1/self/accounttransfers/template?type="tpt"
+	        </code>
+			<code class="method-response">
+{
+  "fromAccountOptions": [
+	{
+		"accountId": 1,
+		"accountNo": "00000001",
+		"accountType": {
+			"id": 2,
+			"code": "accountType.savings",
+			"value": "Savings Account"
+		},
+		"clientId": 1,
+		"clientName": "ABC",
+		"officeId": 1,
+		"officeName": "HEAD OFFICE"
+	},
+	{
+		"accountId": 5,
+		"accountNo": "00000005",
+		"accountType": {
+			"id": 1,
+			"code": "accountType.loan",
+			"value": "Loan Account"
+		},
+		"clientId": 2,
+		"clientName": "XYZ",
+		"officeId": 3,
+		"officeName": "REGIONAL OFFICE"
+	}
+  ],
+ "toAccountOptions": [
+	{
+		"accountId": 2,
+		"accountNo": "00000002",
+		"accountType": {
+			"id": 2,
+			"code": "accountType.savings",
+			"value": "Savings Account"
+		},
+		"clientId": 2,
+		"clientName": "ABC",
+		"officeId": 1,
+		"officeName": "HEAD OFFICE"
+	},
+	{
+		"accountId": 6,
+		"accountNo": "00000006",
+		"accountType": {
+			"id": 1,
+			"code": "accountType.loan",
+			"value": "Loan Account"
+		},
+		"clientId": 3,
+		"clientName": "XYZ",
+		"officeId": 4,
+		"officeName": "REGIONAL OFFICE"
+	}
+  ]
+
+}
+			</code>
+	    </div>
+	</div>
+
+	<a id="selftpttransfer" name="selftpttransfer" class="old-syle-anchor">&nbsp;</a>
+	<div class="method-section">
+	    <div class="method-description">
+	        <h2>Third Party Account Transfer</h2>
+	        <p>Ability to create new third party transfer of monetary funds from one account to another.</p>
+	    </div>
+	    <div class="method-example">
+	        <code class="method-declaration">POST https://DomainName/api/v1/self/accounttransfers?type="tpt"</code>
+	        <code class="method-request">POST self/accounttransfers?type="tpt"
+Content-Type: application/json
+No Request Body:
+{
+"fromOfficeId": 1,
+"fromClientId": 1,
+"fromAccountType": 2,
+"fromAccountId": 1,
+"toOfficeId": 1,
+"toClientId": 2,
+"toAccountType": 2,
+"toAccountId": 2,
+"dateFormat": "dd MMMM yyyy",
+"locale": "en",
+"transferDate": "01 August 2011",
+"transferAmount": "112.45",
+"transferDescription": "A description of the transfer"
+}
+	        </code>
+	        <code class="method-response">
+{
+  "savingsId": 1,
+  "resourceId": 1
+}
+	        </code>
+	    </div>
+	</div>
 			<!-- end of Customer Self Service APIs-->
         </div>
 		<!-- main-content-wrapper -->

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/2bd3b062/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
index bd13574..5751e0e 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
@@ -2702,4 +2702,29 @@ public class CommandWrapperBuilder {
         this.href = "/savingsaccounts/" + accountId + "?commad=updateTaxWithHoldTax";
         return this;
     }
+
+	public CommandWrapperBuilder addSelfServiceBeneficiaryTPT() {
+        this.actionName = "CREATE";
+        this.entityName = "SSBENEFICIARYTPT";
+        this.entityId = null;
+        this.href = "/self/beneficiaries/tpt";
+        return this;
+	}
+
+	public CommandWrapperBuilder updateSelfServiceBeneficiaryTPT(final Long beneficiaryId) {
+        this.actionName = "UPDATE";
+        this.entityName = "SSBENEFICIARYTPT";
+        this.entityId = beneficiaryId;
+        this.href = "/self/beneficiaries/tpt/"+beneficiaryId;
+        return this;
+	}
+
+	public CommandWrapperBuilder deleteSelfServiceBeneficiaryTPT(final Long beneficiaryId) {
+        this.actionName = "DELETE";
+        this.entityName = "SSBENEFICIARYTPT";
+        this.entityId = beneficiaryId;
+        this.href = "/self/beneficiaries/tpt/"+beneficiaryId;
+        return this;
+	}
+
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/2bd3b062/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java
index 75aefb2..58494c2 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java
@@ -79,4 +79,8 @@ public interface ConfigurationDomainService {
     
     boolean isChangeEmiIfRepaymentDateSameAsDisbursementDateEnabled();
     
+    boolean isDailyTPTLimitEnabled();
+    
+    Long getDailyTPTLimit();
+    
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/2bd3b062/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java
index 356d098..67ebdc5 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java
@@ -19,6 +19,7 @@
 package org.apache.fineract.infrastructure.configuration.domain;
 
 import java.util.Date;
+
 import org.apache.commons.lang.StringUtils;
 import org.apache.fineract.infrastructure.cache.domain.CacheType;
 import org.apache.fineract.infrastructure.cache.domain.PlatformCache;
@@ -209,6 +210,7 @@ public class ConfigurationDomainServiceJpa implements ConfigurationDomainService
         return defaultValue;
     }
 
+    @Override
     public boolean isBackdatePenaltiesEnabled() {
         final String propertyName = "backdate-penalties-enabled";
         final GlobalConfigurationProperty property = this.globalConfigurationRepository.findOneByNameWithNotFoundDetection(propertyName);
@@ -263,5 +265,19 @@ public class ConfigurationDomainServiceJpa implements ConfigurationDomainService
         return property.isEnabled();
     }
 
+	@Override
+	public boolean isDailyTPTLimitEnabled() {
+        final String propertyName = "daily-tpt-limit";
+        final GlobalConfigurationProperty property = this.globalConfigurationRepository.findOneByNameWithNotFoundDetection(propertyName);
+        return property.isEnabled();
+	}
+
+	@Override
+	public Long getDailyTPTLimit() {
+        final String propertyName = "daily-tpt-limit";
+        final GlobalConfigurationProperty property = this.globalConfigurationRepository.findOneByNameWithNotFoundDetection(propertyName);
+        return property.getValue();
+	}
+
   
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/2bd3b062/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/TenantDatabaseUpgradeService.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/TenantDatabaseUpgradeService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/TenantDatabaseUpgradeService.java
index 8bf9199..740dcb5 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/TenantDatabaseUpgradeService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/TenantDatabaseUpgradeService.java
@@ -74,7 +74,7 @@ public class TenantDatabaseUpgradeService {
                     flyway.migrate();
                 } catch (FlywayException e) {
                     String betterMessage = e.getMessage() + "; for Tenant DB URL: " + connectionProtocol + ", username: "
-                            + connection.getSchemaPassword();
+                            + connection.getSchemaUsername();
                     throw new FlywayException(betterMessage, e.getCause());
                 }
             }

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/2bd3b062/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedularWritePlatformServiceJpaRepositoryImpl.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedularWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedularWritePlatformServiceJpaRepositoryImpl.java
index f4ac6ac..7e5d5de 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedularWritePlatformServiceJpaRepositoryImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedularWritePlatformServiceJpaRepositoryImpl.java
@@ -136,11 +136,11 @@ public class SchedularWritePlatformServiceJpaRepositoryImpl implements Schedular
         boolean isStopExecution = false;
         final ScheduledJobDetail scheduledJobDetail = this.scheduledJobDetailsRepository.findByJobKeyWithLock(jobKey);
         if (scheduledJobDetail.isCurrentlyRunning()
-                || (triggerType == SchedulerServiceConstants.TRIGGER_TYPE_CRON && (scheduledJobDetail.getNextRunTime().after(new Date())))) {
+                || (triggerType.equals(SchedulerServiceConstants.TRIGGER_TYPE_CRON) && (scheduledJobDetail.getNextRunTime().after(new Date())))) {
             isStopExecution = true;
         }
         final SchedulerDetail schedulerDetail = retriveSchedulerDetail();
-        if (triggerType == SchedulerServiceConstants.TRIGGER_TYPE_CRON && schedulerDetail.isSuspended()) {
+        if (triggerType.equals(SchedulerServiceConstants.TRIGGER_TYPE_CRON) && schedulerDetail.isSuspended()) {
             scheduledJobDetail.updateTriggerMisfired(true);
             isStopExecution = true;
         } else if (!isStopExecution) {

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/2bd3b062/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersReadPlatformService.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersReadPlatformService.java
index c053b01..0434261 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersReadPlatformService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersReadPlatformService.java
@@ -18,12 +18,14 @@
  */
 package org.apache.fineract.portfolio.account.service;
 
+import java.math.BigDecimal;
 import java.util.Collection;
 
 import org.apache.fineract.infrastructure.core.service.Page;
 import org.apache.fineract.infrastructure.core.service.SearchParameters;
 import org.apache.fineract.portfolio.account.PortfolioAccountType;
 import org.apache.fineract.portfolio.account.data.AccountTransferData;
+import org.joda.time.LocalDate;
 
 public interface AccountTransfersReadPlatformService {
 
@@ -42,4 +44,7 @@ public interface AccountTransfersReadPlatformService {
     
     AccountTransferData retrieveRefundByTransferTemplate(Long fromOfficeId, Long fromClientId, Long fromAccountId, Integer fromAccountType,
             Long toOfficeId, Long toClientId, Long toAccountId, Integer toAccountType);
+
+	BigDecimal getTotalTransactionAmount(Long accountId, Integer accountType,
+			LocalDate transactionDate);
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/2bd3b062/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersReadPlatformServiceImpl.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersReadPlatformServiceImpl.java
index 9dbe4c9..08af091 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersReadPlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersReadPlatformServiceImpl.java
@@ -45,6 +45,8 @@ import org.apache.fineract.portfolio.account.exception.AccountTransferNotFoundEx
 import org.apache.fineract.portfolio.client.data.ClientData;
 import org.apache.fineract.portfolio.client.service.ClientReadPlatformService;
 import org.joda.time.LocalDate;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.dao.EmptyResultDataAccessException;
 import org.springframework.jdbc.core.JdbcTemplate;
@@ -53,485 +55,643 @@ import org.springframework.stereotype.Service;
 import org.springframework.util.CollectionUtils;
 
 @Service
-public class AccountTransfersReadPlatformServiceImpl implements AccountTransfersReadPlatformService {
-
-    private final JdbcTemplate jdbcTemplate;
-    private final ClientReadPlatformService clientReadPlatformService;
-    private final OfficeReadPlatformService officeReadPlatformService;
-    private final PortfolioAccountReadPlatformService portfolioAccountReadPlatformService;
-
-    // mapper
-    private final AccountTransfersMapper accountTransfersMapper;
-
-    // pagination
-    private final PaginationHelper<AccountTransferData> paginationHelper = new PaginationHelper<>();
-
-    @Autowired
-    public AccountTransfersReadPlatformServiceImpl(final RoutingDataSource dataSource,
-            final ClientReadPlatformService clientReadPlatformService, final OfficeReadPlatformService officeReadPlatformService,
-            final PortfolioAccountReadPlatformService portfolioAccountReadPlatformService) {
-        this.jdbcTemplate = new JdbcTemplate(dataSource);
-        this.clientReadPlatformService = clientReadPlatformService;
-        this.officeReadPlatformService = officeReadPlatformService;
-        this.portfolioAccountReadPlatformService = portfolioAccountReadPlatformService;
-
-        this.accountTransfersMapper = new AccountTransfersMapper();
-    }
-
-    @Override
-    public AccountTransferData retrieveTemplate(final Long fromOfficeId, final Long fromClientId, final Long fromAccountId,
-            final Integer fromAccountType, final Long toOfficeId, final Long toClientId, final Long toAccountId, final Integer toAccountType) {
-
-        final EnumOptionData loanAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.LOAN);
-        final EnumOptionData savingsAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.SAVINGS);
-
-        final Integer mostRelevantFromAccountType = fromAccountType;
-        final Collection<EnumOptionData> fromAccountTypeOptions = Arrays.asList(savingsAccountType, loanAccountType);
-        final Collection<EnumOptionData> toAccountTypeOptions;
-        if (mostRelevantFromAccountType != null && mostRelevantFromAccountType == 1) {
-            // overpaid loan amt transfer to savings account
-            toAccountTypeOptions = Arrays.asList(savingsAccountType);
-        } else {
-            toAccountTypeOptions = Arrays.asList(loanAccountType, savingsAccountType);
-        }
-        final Integer mostRelevantToAccountType = toAccountType;
-
-        final EnumOptionData fromAccountTypeData = AccountTransferEnumerations.accountType(mostRelevantFromAccountType);
-        final EnumOptionData toAccountTypeData = AccountTransferEnumerations.accountType(mostRelevantToAccountType);
-
-        // from settings
-        OfficeData fromOffice = null;
-        ClientData fromClient = null;
-        PortfolioAccountData fromAccount = null;
-
-        OfficeData toOffice = null;
-        ClientData toClient = null;
-        PortfolioAccountData toAccount = null;
-
-        // template
-        Collection<PortfolioAccountData> fromAccountOptions = null;
-        Collection<PortfolioAccountData> toAccountOptions = null;
-
-        Long mostRelevantFromOfficeId = fromOfficeId;
-        Long mostRelevantFromClientId = fromClientId;
-
-        Long mostRelevantToOfficeId = toOfficeId;
-        Long mostRelevantToClientId = toClientId;
-
-        if (fromAccountId != null) {
-            Integer accountType;
-            if (mostRelevantFromAccountType == 1) {
-                accountType = PortfolioAccountType.LOAN.getValue();
-            } else {
-                accountType = PortfolioAccountType.SAVINGS.getValue();
-            }
-            fromAccount = this.portfolioAccountReadPlatformService.retrieveOne(fromAccountId, accountType);
-
-            // override provided fromClient with client of account
-            mostRelevantFromClientId = fromAccount.clientId();
-        }
-
-        if (mostRelevantFromClientId != null) {
-            fromClient = this.clientReadPlatformService.retrieveOne(mostRelevantFromClientId);
-            mostRelevantFromOfficeId = fromClient.officeId();
-            long[] loanStatus = null;
-            if (mostRelevantFromAccountType == 1) {
-                loanStatus = new long[] { 300, 700 };
-            }
-            PortfolioAccountDTO portfolioAccountDTO = new PortfolioAccountDTO(mostRelevantFromAccountType, mostRelevantFromClientId,
-                    loanStatus);
-            fromAccountOptions = this.portfolioAccountReadPlatformService.retrieveAllForLookup(portfolioAccountDTO);
-        }
-
-        Collection<OfficeData> fromOfficeOptions = null;
-        Collection<ClientData> fromClientOptions = null;
-        if (mostRelevantFromOfficeId != null) {
-            fromOffice = this.officeReadPlatformService.retrieveOffice(mostRelevantFromOfficeId);
-            fromOfficeOptions = this.officeReadPlatformService.retrieveAllOfficesForDropdown();
-            fromClientOptions = this.clientReadPlatformService.retrieveAllForLookupByOfficeId(mostRelevantFromOfficeId);
-        }
-
-        // defaults
-        final LocalDate transferDate = DateUtils.getLocalDateOfTenant();
-        Collection<OfficeData> toOfficeOptions = fromOfficeOptions;
-        Collection<ClientData> toClientOptions = null;
-
-        if (toAccountId != null && fromAccount != null) {
-            toAccount = this.portfolioAccountReadPlatformService.retrieveOne(toAccountId, mostRelevantToAccountType,
-                    fromAccount.currencyCode());
-            mostRelevantToClientId = toAccount.clientId();
-        }
-
-        if (mostRelevantToClientId != null) {
-            toClient = this.clientReadPlatformService.retrieveOne(mostRelevantToClientId);
-            mostRelevantToOfficeId = toClient.officeId();
-
-            toClientOptions = this.clientReadPlatformService.retrieveAllForLookupByOfficeId(mostRelevantToOfficeId);
-
-            toAccountOptions = retrieveToAccounts(fromAccount, mostRelevantToAccountType, mostRelevantToClientId);
-        }
-
-        if (mostRelevantToOfficeId != null) {
-            toOffice = this.officeReadPlatformService.retrieveOffice(mostRelevantToOfficeId);
-            toOfficeOptions = this.officeReadPlatformService.retrieveAllOfficesForDropdown();
-
-            toClientOptions = this.clientReadPlatformService.retrieveAllForLookupByOfficeId(mostRelevantToOfficeId);
-            if (toClientOptions != null && toClientOptions.size() == 1) {
-                toClient = new ArrayList<>(toClientOptions).get(0);
-
-                toAccountOptions = retrieveToAccounts(fromAccount, mostRelevantToAccountType, mostRelevantToClientId);
-            }
-        }
-
-        return AccountTransferData.template(fromOffice, fromClient, fromAccountTypeData, fromAccount, transferDate, toOffice, toClient,
-                toAccountTypeData, toAccount, fromOfficeOptions, fromClientOptions, fromAccountTypeOptions, fromAccountOptions,
-                toOfficeOptions, toClientOptions, toAccountTypeOptions, toAccountOptions);
-    }
-
-    private Collection<PortfolioAccountData> retrieveToAccounts(final PortfolioAccountData excludeThisAccountFromOptions,
-            final Integer toAccountType, final Long toClientId) {
-
-        final String currencyCode = excludeThisAccountFromOptions != null ? excludeThisAccountFromOptions.currencyCode() : null;
-
-        PortfolioAccountDTO portfolioAccountDTO = new PortfolioAccountDTO(toAccountType, toClientId, currencyCode, null, null);
-        Collection<PortfolioAccountData> accountOptions = this.portfolioAccountReadPlatformService
-                .retrieveAllForLookup(portfolioAccountDTO);
-        if (!CollectionUtils.isEmpty(accountOptions)) {
-            accountOptions.remove(excludeThisAccountFromOptions);
-        } else {
-            accountOptions = null;
-        }
-
-        return accountOptions;
-    }
-
-    @Override
-    public Page<AccountTransferData> retrieveAll(final SearchParameters searchParameters, final Long accountDetailId) {
-
-        final StringBuilder sqlBuilder = new StringBuilder(200);
-        sqlBuilder.append("select SQL_CALC_FOUND_ROWS ");
-        sqlBuilder.append(this.accountTransfersMapper.schema());
-        Object[] finalObjectArray = {};
-        if (accountDetailId != null) {
-            sqlBuilder.append(" where att.account_transfer_details_id=?");
-            finalObjectArray = new Object[] { accountDetailId };
-        }
-
-        if (searchParameters.isOrderByRequested()) {
-            sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
-
-            if (searchParameters.isSortOrderProvided()) {
-                sqlBuilder.append(' ').append(searchParameters.getSortOrder());
-            }
-        }
-
-        if (searchParameters.isLimited()) {
-            sqlBuilder.append(" limit ").append(searchParameters.getLimit());
-            if (searchParameters.isOffset()) {
-                sqlBuilder.append(" offset ").append(searchParameters.getOffset());
-            }
-        }
-
-        final String sqlCountRows = "SELECT FOUND_ROWS()";
-        return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlCountRows, sqlBuilder.toString(), finalObjectArray,
-                this.accountTransfersMapper);
-    }
-
-    @Override
-    public AccountTransferData retrieveOne(final Long transferId) {
-
-        try {
-            final String sql = "select " + this.accountTransfersMapper.schema() + " where att.id = ?";
-
-            return this.jdbcTemplate.queryForObject(sql, this.accountTransfersMapper, new Object[] { transferId });
-        } catch (final EmptyResultDataAccessException e) {
-            throw new AccountTransferNotFoundException(transferId);
-        }
-    }
-
-    @Override
-    public Collection<Long> fetchPostInterestTransactionIds(final Long accountId) {
-        final String sql = "select att.from_savings_transaction_id from m_account_transfer_transaction att inner join m_account_transfer_details atd on atd.id = att.account_transfer_details_id where atd.from_savings_account_id=? and att.is_reversed =0 and atd.transfer_type = ?";
-
-        final List<Long> transactionId = this.jdbcTemplate.queryForList(sql, Long.class, accountId,
-                AccountTransferType.INTEREST_TRANSFER.getValue());
-
-        return transactionId;
-    }
-
-    private static final class AccountTransfersMapper implements RowMapper<AccountTransferData> {
-
-        private final String schemaSql;
-
-        public AccountTransfersMapper() {
-            final StringBuilder sqlBuilder = new StringBuilder(400);
-            sqlBuilder.append("att.id as id, att.is_reversed as isReversed,");
-            sqlBuilder.append("att.transaction_date as transferDate, att.amount as transferAmount,");
-            sqlBuilder.append("att.description as transferDescription,");
-            sqlBuilder.append("att.currency_code as currencyCode, att.currency_digits as currencyDigits,");
-            sqlBuilder.append("att.currency_multiplesof as inMultiplesOf, ");
-            sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
-            sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol, ");
-            sqlBuilder.append("fromoff.id as fromOfficeId, fromoff.name as fromOfficeName,");
-            sqlBuilder.append("tooff.id as toOfficeId, tooff.name as toOfficeName,");
-            sqlBuilder.append("fromclient.id as fromClientId, fromclient.display_name as fromClientName,");
-            sqlBuilder.append("toclient.id as toClientId, toclient.display_name as toClientName,");
-            sqlBuilder.append("fromsavacc.id as fromSavingsAccountId, fromsavacc.account_no as fromSavingsAccountNo,");
-            sqlBuilder.append("fromloanacc.id as fromLoanAccountId, fromloanacc.account_no as fromLoanAccountNo,");
-            sqlBuilder.append("tosavacc.id as toSavingsAccountId, tosavacc.account_no as toSavingsAccountNo,");
-            sqlBuilder.append("toloanacc.id as toLoanAccountId, toloanacc.account_no as toLoanAccountNo,");
-            sqlBuilder.append("fromsavtran.id as fromSavingsAccountTransactionId,");
-            sqlBuilder.append("fromsavtran.transaction_type_enum as fromSavingsAccountTransactionType,");
-            sqlBuilder.append("tosavtran.id as toSavingsAccountTransactionId,");
-            sqlBuilder.append("tosavtran.transaction_type_enum as toSavingsAccountTransactionType");
-            sqlBuilder.append(" FROM m_account_transfer_transaction att ");
-            sqlBuilder.append("left join m_account_transfer_details atd on atd.id = att.account_transfer_details_id ");
-            sqlBuilder.append("join m_currency curr on curr.code = att.currency_code ");
-            sqlBuilder.append("join m_office fromoff on fromoff.id = atd.from_office_id ");
-            sqlBuilder.append("join m_office tooff on tooff.id = atd.to_office_id ");
-            sqlBuilder.append("join m_client fromclient on fromclient.id = atd.from_client_id ");
-            sqlBuilder.append("join m_client toclient on toclient.id = atd.to_client_id ");
-            sqlBuilder.append("left join m_savings_account fromsavacc on fromsavacc.id = atd.from_savings_account_id ");
-            sqlBuilder.append("left join m_loan fromloanacc on fromloanacc.id = atd.from_loan_account_id ");
-            sqlBuilder.append("left join m_savings_account tosavacc on tosavacc.id = atd.to_savings_account_id ");
-            sqlBuilder.append("left join m_loan toloanacc on toloanacc.id = atd.to_loan_account_id ");
-            sqlBuilder.append("left join m_savings_account_transaction fromsavtran on fromsavtran.id = att.from_savings_transaction_id ");
-            sqlBuilder.append("left join m_savings_account_transaction tosavtran on tosavtran.id = att.to_savings_transaction_id ");
-            sqlBuilder.append("left join m_loan_transaction fromloantran on fromloantran.id = att.from_savings_transaction_id ");
-            sqlBuilder.append("left join m_loan_transaction toloantran on toloantran.id = att.to_savings_transaction_id ");
-
-            this.schemaSql = sqlBuilder.toString();
-        }
-
-        public String schema() {
-            return this.schemaSql;
-        }
-
-        @Override
-        public AccountTransferData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
-
-            final Long id = rs.getLong("id");
-            final boolean reversed = rs.getBoolean("isReversed");
-
-            final LocalDate transferDate = JdbcSupport.getLocalDate(rs, "transferDate");
-            final BigDecimal transferAmount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "transferAmount");
-            final String transferDescription = rs.getString("transferDescription");
-
-            final String currencyCode = rs.getString("currencyCode");
-            final String currencyName = rs.getString("currencyName");
-            final String currencyNameCode = rs.getString("currencyNameCode");
-            final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
-            final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
-            final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
-            final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf,
-                    currencyDisplaySymbol, currencyNameCode);
-
-            final Long fromOfficeId = JdbcSupport.getLong(rs, "fromOfficeId");
-            final String fromOfficeName = rs.getString("fromOfficeName");
-            final OfficeData fromOffice = OfficeData.dropdown(fromOfficeId, fromOfficeName, null);
-
-            final Long toOfficeId = JdbcSupport.getLong(rs, "toOfficeId");
-            final String toOfficeName = rs.getString("toOfficeName");
-            final OfficeData toOffice = OfficeData.dropdown(toOfficeId, toOfficeName, null);
-
-            final Long fromClientId = JdbcSupport.getLong(rs, "fromClientId");
-            final String fromClientName = rs.getString("fromClientName");
-            final ClientData fromClient = ClientData.lookup(fromClientId, fromClientName, fromOfficeId, fromOfficeName);
-
-            final Long toClientId = JdbcSupport.getLong(rs, "toClientId");
-            final String toClientName = rs.getString("toClientName");
-            final ClientData toClient = ClientData.lookup(toClientId, toClientName, toOfficeId, toOfficeName);
-
-            final Long fromSavingsAccountId = JdbcSupport.getLong(rs, "fromSavingsAccountId");
-            final String fromSavingsAccountNo = rs.getString("fromSavingsAccountNo");
-            final Long fromLoanAccountId = JdbcSupport.getLong(rs, "fromLoanAccountId");
-            final String fromLoanAccountNo = rs.getString("fromLoanAccountNo");
-            PortfolioAccountData fromAccount = null;
-            EnumOptionData fromAccountType = null;
-            if (fromSavingsAccountId != null) {
-                fromAccount = PortfolioAccountData.lookup(fromSavingsAccountId, fromSavingsAccountNo);
-                fromAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.SAVINGS);
-            } else if (fromLoanAccountId != null) {
-                fromAccount = PortfolioAccountData.lookup(fromLoanAccountId, fromLoanAccountNo);
-                fromAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.LOAN);
-            }
-
-            PortfolioAccountData toAccount = null;
-            EnumOptionData toAccountType = null;
-            final Long toSavingsAccountId = JdbcSupport.getLong(rs, "toSavingsAccountId");
-            final String toSavingsAccountNo = rs.getString("toSavingsAccountNo");
-            final Long toLoanAccountId = JdbcSupport.getLong(rs, "toLoanAccountId");
-            final String toLoanAccountNo = rs.getString("toLoanAccountNo");
-
-            if (toSavingsAccountId != null) {
-                toAccount = PortfolioAccountData.lookup(toSavingsAccountId, toSavingsAccountNo);
-                toAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.SAVINGS);
-            } else if (toLoanAccountId != null) {
-                toAccount = PortfolioAccountData.lookup(toLoanAccountId, toLoanAccountNo);
-                toAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.LOAN);
-            }
-
-            return AccountTransferData.instance(id, reversed, transferDate, currency, transferAmount, transferDescription, fromOffice,
-                    toOffice, fromClient, toClient, fromAccountType, fromAccount, toAccountType, toAccount);
-        }
-    }
-
-    @Override
-    public boolean isAccountTransfer(final Long transactionId, final PortfolioAccountType accountType) {
-        final StringBuilder sql = new StringBuilder("select count(*) from m_account_transfer_transaction at where ");
-        if (accountType.isLoanAccount()) {
-            sql.append("at.from_loan_transaction_id=").append(transactionId).append(" or at.to_loan_transaction_id=").append(transactionId);
-        } else {
-            sql.append("at.from_savings_transaction_id=").append(transactionId).append(" or at.to_savings_transaction_id=")
-                    .append(transactionId);
-        }
-
-        @SuppressWarnings("deprecation")
-        final int count = this.jdbcTemplate.queryForInt(sql.toString());
-        return count > 0;
-    }
-
-    @Override
-    public Page<AccountTransferData> retrieveByStandingInstruction(final Long id, final SearchParameters searchParameters) {
-
-        final StringBuilder sqlBuilder = new StringBuilder(200);
-        sqlBuilder.append("select SQL_CALC_FOUND_ROWS ");
-        sqlBuilder
-                .append(this.accountTransfersMapper.schema())
-                .append(" join m_account_transfer_standing_instructions atsi on atsi.account_transfer_details_id = att.account_transfer_details_id ");
-        sqlBuilder.append(" where atsi.id = ?");
-
-        if (searchParameters != null) {
-            if (searchParameters.isOrderByRequested()) {
-                sqlBuilder.append(" order by ").append(searchParameters.getOrderBy());
-
-                if (searchParameters.isSortOrderProvided()) {
-                    sqlBuilder.append(' ').append(searchParameters.getSortOrder());
-                }
-            }
-
-            if (searchParameters.isLimited()) {
-                sqlBuilder.append(" limit ").append(searchParameters.getLimit());
-                if (searchParameters.isOffset()) {
-                    sqlBuilder.append(" offset ").append(searchParameters.getOffset());
-                }
-            }
-        }
-
-        final Object[] finalObjectArray = { id };
-        final String sqlCountRows = "SELECT FOUND_ROWS()";
-        return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlCountRows, sqlBuilder.toString(), finalObjectArray,
-                this.accountTransfersMapper);
-    }
-
-    @Override
-    public AccountTransferData retrieveRefundByTransferTemplate(final Long fromOfficeId, final Long fromClientId, final Long fromAccountId,
-            final Integer fromAccountType, final Long toOfficeId, final Long toClientId, final Long toAccountId, final Integer toAccountType) {
-        // TODO Auto-generated method stub
-        final EnumOptionData loanAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.LOAN);
-        final EnumOptionData savingsAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.SAVINGS);
-
-        final Integer mostRelevantFromAccountType = fromAccountType;
-        final Collection<EnumOptionData> fromAccountTypeOptions = Arrays.asList(savingsAccountType, loanAccountType);
-        final Collection<EnumOptionData> toAccountTypeOptions;
-        if (mostRelevantFromAccountType == 1) {
-            // overpaid loan amt transfer to savings account
-            toAccountTypeOptions = Arrays.asList(savingsAccountType);
-        } else {
-            toAccountTypeOptions = Arrays.asList(loanAccountType, savingsAccountType);
-        }
-        final Integer mostRelevantToAccountType = toAccountType;
-
-        final EnumOptionData fromAccountTypeData = AccountTransferEnumerations.accountType(mostRelevantFromAccountType);
-        final EnumOptionData toAccountTypeData = AccountTransferEnumerations.accountType(mostRelevantToAccountType);
-
-        // from settings
-        OfficeData fromOffice = null;
-        ClientData fromClient = null;
-        PortfolioAccountData fromAccount = null;
-
-        OfficeData toOffice = null;
-        ClientData toClient = null;
-        PortfolioAccountData toAccount = null;
-
-        // template
-        Collection<PortfolioAccountData> fromAccountOptions = null;
-        Collection<PortfolioAccountData> toAccountOptions = null;
-
-        Long mostRelevantFromOfficeId = fromOfficeId;
-        Long mostRelevantFromClientId = fromClientId;
-
-        Long mostRelevantToOfficeId = toOfficeId;
-        Long mostRelevantToClientId = toClientId;
-
-        if (fromAccountId != null) {
-            Integer accountType;
-            if (mostRelevantFromAccountType == 1) {
-                accountType = PortfolioAccountType.LOAN.getValue();
-            } else {
-                accountType = PortfolioAccountType.SAVINGS.getValue();
-            }
-            fromAccount = this.portfolioAccountReadPlatformService.retrieveOneByPaidInAdvance(fromAccountId, accountType);
-
-            // override provided fromClient with client of account
-            mostRelevantFromClientId = fromAccount.clientId();
-        }
-
-        if (mostRelevantFromClientId != null) {
-            fromClient = this.clientReadPlatformService.retrieveOne(mostRelevantFromClientId);
-            mostRelevantFromOfficeId = fromClient.officeId();
-            long[] loanStatus = null;
-            if (mostRelevantFromAccountType == 1) {
-                loanStatus = new long[] { 300, 700 };
-            }
-            PortfolioAccountDTO portfolioAccountDTO = new PortfolioAccountDTO(mostRelevantFromAccountType, mostRelevantFromClientId,
-                    loanStatus);
-            fromAccountOptions = this.portfolioAccountReadPlatformService.retrieveAllForLookup(portfolioAccountDTO);
-        }
-
-        Collection<OfficeData> fromOfficeOptions = null;
-        Collection<ClientData> fromClientOptions = null;
-        if (mostRelevantFromOfficeId != null) {
-            fromOffice = this.officeReadPlatformService.retrieveOffice(mostRelevantFromOfficeId);
-            fromOfficeOptions = this.officeReadPlatformService.retrieveAllOfficesForDropdown();
-            fromClientOptions = this.clientReadPlatformService.retrieveAllForLookupByOfficeId(mostRelevantFromOfficeId);
-        }
-
-        // defaults
-        final LocalDate transferDate = DateUtils.getLocalDateOfTenant();
-        Collection<OfficeData> toOfficeOptions = fromOfficeOptions;
-        Collection<ClientData> toClientOptions = null;
-
-        if (toAccountId != null && fromAccount != null) {
-            toAccount = this.portfolioAccountReadPlatformService.retrieveOne(toAccountId, mostRelevantToAccountType,
-                    fromAccount.currencyCode());
-            mostRelevantToClientId = toAccount.clientId();
-        }
-
-        if (mostRelevantToClientId != null) {
-            toClient = this.clientReadPlatformService.retrieveOne(mostRelevantToClientId);
-            mostRelevantToOfficeId = toClient.officeId();
-
-            toClientOptions = this.clientReadPlatformService.retrieveAllForLookupByOfficeId(mostRelevantToOfficeId);
-
-            toAccountOptions = retrieveToAccounts(fromAccount, mostRelevantToAccountType, mostRelevantToClientId);
-        }
-
-        if (mostRelevantToOfficeId != null) {
-            toOffice = this.officeReadPlatformService.retrieveOffice(mostRelevantToOfficeId);
-            toOfficeOptions = this.officeReadPlatformService.retrieveAllOfficesForDropdown();
-
-            toClientOptions = this.clientReadPlatformService.retrieveAllForLookupByOfficeId(mostRelevantToOfficeId);
-            if (toClientOptions != null && toClientOptions.size() == 1) {
-                toClient = new ArrayList<>(toClientOptions).get(0);
-
-                toAccountOptions = retrieveToAccounts(fromAccount, mostRelevantToAccountType, mostRelevantToClientId);
-            }
-        }
-
-        return AccountTransferData.template(fromOffice, fromClient, fromAccountTypeData, fromAccount, transferDate, toOffice, toClient,
-                toAccountTypeData, toAccount, fromOfficeOptions, fromClientOptions, fromAccountTypeOptions, fromAccountOptions,
-                toOfficeOptions, toClientOptions, toAccountTypeOptions, toAccountOptions);
-    }
+public class AccountTransfersReadPlatformServiceImpl implements
+		AccountTransfersReadPlatformService {
+
+	private final JdbcTemplate jdbcTemplate;
+	private final ClientReadPlatformService clientReadPlatformService;
+	private final OfficeReadPlatformService officeReadPlatformService;
+	private final PortfolioAccountReadPlatformService portfolioAccountReadPlatformService;
+
+	// mapper
+	private final AccountTransfersMapper accountTransfersMapper;
+
+	// pagination
+	private final PaginationHelper<AccountTransferData> paginationHelper = new PaginationHelper<>();
+	private final DateTimeFormatter formatter = DateTimeFormat
+			.forPattern("yyyy-MM-dd");
+
+	@Autowired
+	public AccountTransfersReadPlatformServiceImpl(
+			final RoutingDataSource dataSource,
+			final ClientReadPlatformService clientReadPlatformService,
+			final OfficeReadPlatformService officeReadPlatformService,
+			final PortfolioAccountReadPlatformService portfolioAccountReadPlatformService) {
+		this.jdbcTemplate = new JdbcTemplate(dataSource);
+		this.clientReadPlatformService = clientReadPlatformService;
+		this.officeReadPlatformService = officeReadPlatformService;
+		this.portfolioAccountReadPlatformService = portfolioAccountReadPlatformService;
+
+		this.accountTransfersMapper = new AccountTransfersMapper();
+	}
+
+	@Override
+	public AccountTransferData retrieveTemplate(final Long fromOfficeId,
+			final Long fromClientId, final Long fromAccountId,
+			final Integer fromAccountType, final Long toOfficeId,
+			final Long toClientId, final Long toAccountId,
+			final Integer toAccountType) {
+
+		final EnumOptionData loanAccountType = AccountTransferEnumerations
+				.accountType(PortfolioAccountType.LOAN);
+		final EnumOptionData savingsAccountType = AccountTransferEnumerations
+				.accountType(PortfolioAccountType.SAVINGS);
+
+		final Integer mostRelevantFromAccountType = fromAccountType;
+		final Collection<EnumOptionData> fromAccountTypeOptions = Arrays
+				.asList(savingsAccountType, loanAccountType);
+		final Collection<EnumOptionData> toAccountTypeOptions;
+		if (mostRelevantFromAccountType != null
+				&& mostRelevantFromAccountType == 1) {
+			// overpaid loan amt transfer to savings account
+			toAccountTypeOptions = Arrays.asList(savingsAccountType);
+		} else {
+			toAccountTypeOptions = Arrays.asList(loanAccountType,
+					savingsAccountType);
+		}
+		final Integer mostRelevantToAccountType = toAccountType;
+
+		final EnumOptionData fromAccountTypeData = AccountTransferEnumerations
+				.accountType(mostRelevantFromAccountType);
+		final EnumOptionData toAccountTypeData = AccountTransferEnumerations
+				.accountType(mostRelevantToAccountType);
+
+		// from settings
+		OfficeData fromOffice = null;
+		ClientData fromClient = null;
+		PortfolioAccountData fromAccount = null;
+
+		OfficeData toOffice = null;
+		ClientData toClient = null;
+		PortfolioAccountData toAccount = null;
+
+		// template
+		Collection<PortfolioAccountData> fromAccountOptions = null;
+		Collection<PortfolioAccountData> toAccountOptions = null;
+
+		Long mostRelevantFromOfficeId = fromOfficeId;
+		Long mostRelevantFromClientId = fromClientId;
+
+		Long mostRelevantToOfficeId = toOfficeId;
+		Long mostRelevantToClientId = toClientId;
+
+		if (fromAccountId != null) {
+			Integer accountType;
+			if (mostRelevantFromAccountType == 1) {
+				accountType = PortfolioAccountType.LOAN.getValue();
+			} else {
+				accountType = PortfolioAccountType.SAVINGS.getValue();
+			}
+			fromAccount = this.portfolioAccountReadPlatformService.retrieveOne(
+					fromAccountId, accountType);
+
+			// override provided fromClient with client of account
+			mostRelevantFromClientId = fromAccount.clientId();
+		}
+
+		if (mostRelevantFromClientId != null) {
+			fromClient = this.clientReadPlatformService
+					.retrieveOne(mostRelevantFromClientId);
+			mostRelevantFromOfficeId = fromClient.officeId();
+			long[] loanStatus = null;
+			if (mostRelevantFromAccountType == 1) {
+				loanStatus = new long[] { 300, 700 };
+			}
+			PortfolioAccountDTO portfolioAccountDTO = new PortfolioAccountDTO(
+					mostRelevantFromAccountType, mostRelevantFromClientId,
+					loanStatus);
+			fromAccountOptions = this.portfolioAccountReadPlatformService
+					.retrieveAllForLookup(portfolioAccountDTO);
+		}
+
+		Collection<OfficeData> fromOfficeOptions = null;
+		Collection<ClientData> fromClientOptions = null;
+		if (mostRelevantFromOfficeId != null) {
+			fromOffice = this.officeReadPlatformService
+					.retrieveOffice(mostRelevantFromOfficeId);
+			fromOfficeOptions = this.officeReadPlatformService
+					.retrieveAllOfficesForDropdown();
+			fromClientOptions = this.clientReadPlatformService
+					.retrieveAllForLookupByOfficeId(mostRelevantFromOfficeId);
+		}
+
+		// defaults
+		final LocalDate transferDate = DateUtils.getLocalDateOfTenant();
+		Collection<OfficeData> toOfficeOptions = fromOfficeOptions;
+		Collection<ClientData> toClientOptions = null;
+
+		if (toAccountId != null && fromAccount != null) {
+			toAccount = this.portfolioAccountReadPlatformService.retrieveOne(
+					toAccountId, mostRelevantToAccountType,
+					fromAccount.currencyCode());
+			mostRelevantToClientId = toAccount.clientId();
+		}
+
+		if (mostRelevantToClientId != null) {
+			toClient = this.clientReadPlatformService
+					.retrieveOne(mostRelevantToClientId);
+			mostRelevantToOfficeId = toClient.officeId();
+
+			toClientOptions = this.clientReadPlatformService
+					.retrieveAllForLookupByOfficeId(mostRelevantToOfficeId);
+
+			toAccountOptions = retrieveToAccounts(fromAccount,
+					mostRelevantToAccountType, mostRelevantToClientId);
+		}
+
+		if (mostRelevantToOfficeId != null) {
+			toOffice = this.officeReadPlatformService
+					.retrieveOffice(mostRelevantToOfficeId);
+			toOfficeOptions = this.officeReadPlatformService
+					.retrieveAllOfficesForDropdown();
+
+			toClientOptions = this.clientReadPlatformService
+					.retrieveAllForLookupByOfficeId(mostRelevantToOfficeId);
+			if (toClientOptions != null && toClientOptions.size() == 1) {
+				toClient = new ArrayList<>(toClientOptions).get(0);
+
+				toAccountOptions = retrieveToAccounts(fromAccount,
+						mostRelevantToAccountType, mostRelevantToClientId);
+			}
+		}
+
+		return AccountTransferData.template(fromOffice, fromClient,
+				fromAccountTypeData, fromAccount, transferDate, toOffice,
+				toClient, toAccountTypeData, toAccount, fromOfficeOptions,
+				fromClientOptions, fromAccountTypeOptions, fromAccountOptions,
+				toOfficeOptions, toClientOptions, toAccountTypeOptions,
+				toAccountOptions);
+	}
+
+	private Collection<PortfolioAccountData> retrieveToAccounts(
+			final PortfolioAccountData excludeThisAccountFromOptions,
+			final Integer toAccountType, final Long toClientId) {
+
+		final String currencyCode = excludeThisAccountFromOptions != null ? excludeThisAccountFromOptions
+				.currencyCode() : null;
+
+		PortfolioAccountDTO portfolioAccountDTO = new PortfolioAccountDTO(
+				toAccountType, toClientId, currencyCode, null, null);
+		Collection<PortfolioAccountData> accountOptions = this.portfolioAccountReadPlatformService
+				.retrieveAllForLookup(portfolioAccountDTO);
+		if (!CollectionUtils.isEmpty(accountOptions)) {
+			accountOptions.remove(excludeThisAccountFromOptions);
+		} else {
+			accountOptions = null;
+		}
+
+		return accountOptions;
+	}
+
+	@Override
+	public Page<AccountTransferData> retrieveAll(
+			final SearchParameters searchParameters, final Long accountDetailId) {
+
+		final StringBuilder sqlBuilder = new StringBuilder(200);
+		sqlBuilder.append("select SQL_CALC_FOUND_ROWS ");
+		sqlBuilder.append(this.accountTransfersMapper.schema());
+		Object[] finalObjectArray = {};
+		if (accountDetailId != null) {
+			sqlBuilder.append(" where att.account_transfer_details_id=?");
+			finalObjectArray = new Object[] { accountDetailId };
+		}
+
+		if (searchParameters.isOrderByRequested()) {
+			sqlBuilder.append(" order by ").append(
+					searchParameters.getOrderBy());
+
+			if (searchParameters.isSortOrderProvided()) {
+				sqlBuilder.append(' ').append(searchParameters.getSortOrder());
+			}
+		}
+
+		if (searchParameters.isLimited()) {
+			sqlBuilder.append(" limit ").append(searchParameters.getLimit());
+			if (searchParameters.isOffset()) {
+				sqlBuilder.append(" offset ").append(
+						searchParameters.getOffset());
+			}
+		}
+
+		final String sqlCountRows = "SELECT FOUND_ROWS()";
+		return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlCountRows,
+				sqlBuilder.toString(), finalObjectArray,
+				this.accountTransfersMapper);
+	}
+
+	@Override
+	public AccountTransferData retrieveOne(final Long transferId) {
+
+		try {
+			final String sql = "select " + this.accountTransfersMapper.schema()
+					+ " where att.id = ?";
+
+			return this.jdbcTemplate.queryForObject(sql,
+					this.accountTransfersMapper, new Object[] { transferId });
+		} catch (final EmptyResultDataAccessException e) {
+			throw new AccountTransferNotFoundException(transferId);
+		}
+	}
+
+	@Override
+	public Collection<Long> fetchPostInterestTransactionIds(final Long accountId) {
+		final String sql = "select att.from_savings_transaction_id from m_account_transfer_transaction att inner join m_account_transfer_details atd on atd.id = att.account_transfer_details_id where atd.from_savings_account_id=? and att.is_reversed =0 and atd.transfer_type = ?";
+
+		final List<Long> transactionId = this.jdbcTemplate.queryForList(sql,
+				Long.class, accountId,
+				AccountTransferType.INTEREST_TRANSFER.getValue());
+
+		return transactionId;
+	}
+
+	private static final class AccountTransfersMapper implements
+			RowMapper<AccountTransferData> {
+
+		private final String schemaSql;
+
+		public AccountTransfersMapper() {
+			final StringBuilder sqlBuilder = new StringBuilder(400);
+			sqlBuilder.append("att.id as id, att.is_reversed as isReversed,");
+			sqlBuilder
+					.append("att.transaction_date as transferDate, att.amount as transferAmount,");
+			sqlBuilder.append("att.description as transferDescription,");
+			sqlBuilder
+					.append("att.currency_code as currencyCode, att.currency_digits as currencyDigits,");
+			sqlBuilder.append("att.currency_multiplesof as inMultiplesOf, ");
+			sqlBuilder
+					.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
+			sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol, ");
+			sqlBuilder
+					.append("fromoff.id as fromOfficeId, fromoff.name as fromOfficeName,");
+			sqlBuilder
+					.append("tooff.id as toOfficeId, tooff.name as toOfficeName,");
+			sqlBuilder
+					.append("fromclient.id as fromClientId, fromclient.display_name as fromClientName,");
+			sqlBuilder
+					.append("toclient.id as toClientId, toclient.display_name as toClientName,");
+			sqlBuilder
+					.append("fromsavacc.id as fromSavingsAccountId, fromsavacc.account_no as fromSavingsAccountNo,");
+			sqlBuilder
+					.append("fromloanacc.id as fromLoanAccountId, fromloanacc.account_no as fromLoanAccountNo,");
+			sqlBuilder
+					.append("tosavacc.id as toSavingsAccountId, tosavacc.account_no as toSavingsAccountNo,");
+			sqlBuilder
+					.append("toloanacc.id as toLoanAccountId, toloanacc.account_no as toLoanAccountNo,");
+			sqlBuilder
+					.append("fromsavtran.id as fromSavingsAccountTransactionId,");
+			sqlBuilder
+					.append("fromsavtran.transaction_type_enum as fromSavingsAccountTransactionType,");
+			sqlBuilder.append("tosavtran.id as toSavingsAccountTransactionId,");
+			sqlBuilder
+					.append("tosavtran.transaction_type_enum as toSavingsAccountTransactionType");
+			sqlBuilder.append(" FROM m_account_transfer_transaction att ");
+			sqlBuilder
+					.append("left join m_account_transfer_details atd on atd.id = att.account_transfer_details_id ");
+			sqlBuilder
+					.append("join m_currency curr on curr.code = att.currency_code ");
+			sqlBuilder
+					.append("join m_office fromoff on fromoff.id = atd.from_office_id ");
+			sqlBuilder
+					.append("join m_office tooff on tooff.id = atd.to_office_id ");
+			sqlBuilder
+					.append("join m_client fromclient on fromclient.id = atd.from_client_id ");
+			sqlBuilder
+					.append("join m_client toclient on toclient.id = atd.to_client_id ");
+			sqlBuilder
+					.append("left join m_savings_account fromsavacc on fromsavacc.id = atd.from_savings_account_id ");
+			sqlBuilder
+					.append("left join m_loan fromloanacc on fromloanacc.id = atd.from_loan_account_id ");
+			sqlBuilder
+					.append("left join m_savings_account tosavacc on tosavacc.id = atd.to_savings_account_id ");
+			sqlBuilder
+					.append("left join m_loan toloanacc on toloanacc.id = atd.to_loan_account_id ");
+			sqlBuilder
+					.append("left join m_savings_account_transaction fromsavtran on fromsavtran.id = att.from_savings_transaction_id ");
+			sqlBuilder
+					.append("left join m_savings_account_transaction tosavtran on tosavtran.id = att.to_savings_transaction_id ");
+			sqlBuilder
+					.append("left join m_loan_transaction fromloantran on fromloantran.id = att.from_savings_transaction_id ");
+			sqlBuilder
+					.append("left join m_loan_transaction toloantran on toloantran.id = att.to_savings_transaction_id ");
+
+			this.schemaSql = sqlBuilder.toString();
+		}
+
+		public String schema() {
+			return this.schemaSql;
+		}
+
+		@Override
+		public AccountTransferData mapRow(final ResultSet rs,
+				@SuppressWarnings("unused") final int rowNum)
+				throws SQLException {
+
+			final Long id = rs.getLong("id");
+			final boolean reversed = rs.getBoolean("isReversed");
+
+			final LocalDate transferDate = JdbcSupport.getLocalDate(rs,
+					"transferDate");
+			final BigDecimal transferAmount = JdbcSupport
+					.getBigDecimalDefaultToZeroIfNull(rs, "transferAmount");
+			final String transferDescription = rs
+					.getString("transferDescription");
+
+			final String currencyCode = rs.getString("currencyCode");
+			final String currencyName = rs.getString("currencyName");
+			final String currencyNameCode = rs.getString("currencyNameCode");
+			final String currencyDisplaySymbol = rs
+					.getString("currencyDisplaySymbol");
+			final Integer currencyDigits = JdbcSupport.getInteger(rs,
+					"currencyDigits");
+			final Integer inMultiplesOf = JdbcSupport.getInteger(rs,
+					"inMultiplesOf");
+			final CurrencyData currency = new CurrencyData(currencyCode,
+					currencyName, currencyDigits, inMultiplesOf,
+					currencyDisplaySymbol, currencyNameCode);
+
+			final Long fromOfficeId = JdbcSupport.getLong(rs, "fromOfficeId");
+			final String fromOfficeName = rs.getString("fromOfficeName");
+			final OfficeData fromOffice = OfficeData.dropdown(fromOfficeId,
+					fromOfficeName, null);
+
+			final Long toOfficeId = JdbcSupport.getLong(rs, "toOfficeId");
+			final String toOfficeName = rs.getString("toOfficeName");
+			final OfficeData toOffice = OfficeData.dropdown(toOfficeId,
+					toOfficeName, null);
+
+			final Long fromClientId = JdbcSupport.getLong(rs, "fromClientId");
+			final String fromClientName = rs.getString("fromClientName");
+			final ClientData fromClient = ClientData.lookup(fromClientId,
+					fromClientName, fromOfficeId, fromOfficeName);
+
+			final Long toClientId = JdbcSupport.getLong(rs, "toClientId");
+			final String toClientName = rs.getString("toClientName");
+			final ClientData toClient = ClientData.lookup(toClientId,
+					toClientName, toOfficeId, toOfficeName);
+
+			final Long fromSavingsAccountId = JdbcSupport.getLong(rs,
+					"fromSavingsAccountId");
+			final String fromSavingsAccountNo = rs
+					.getString("fromSavingsAccountNo");
+			final Long fromLoanAccountId = JdbcSupport.getLong(rs,
+					"fromLoanAccountId");
+			final String fromLoanAccountNo = rs.getString("fromLoanAccountNo");
+			PortfolioAccountData fromAccount = null;
+			EnumOptionData fromAccountType = null;
+			if (fromSavingsAccountId != null) {
+				fromAccount = PortfolioAccountData.lookup(fromSavingsAccountId,
+						fromSavingsAccountNo);
+				fromAccountType = AccountTransferEnumerations
+						.accountType(PortfolioAccountType.SAVINGS);
+			} else if (fromLoanAccountId != null) {
+				fromAccount = PortfolioAccountData.lookup(fromLoanAccountId,
+						fromLoanAccountNo);
+				fromAccountType = AccountTransferEnumerations
+						.accountType(PortfolioAccountType.LOAN);
+			}
+
+			PortfolioAccountData toAccount = null;
+			EnumOptionData toAccountType = null;
+			final Long toSavingsAccountId = JdbcSupport.getLong(rs,
+					"toSavingsAccountId");
+			final String toSavingsAccountNo = rs
+					.getString("toSavingsAccountNo");
+			final Long toLoanAccountId = JdbcSupport.getLong(rs,
+					"toLoanAccountId");
+			final String toLoanAccountNo = rs.getString("toLoanAccountNo");
+
+			if (toSavingsAccountId != null) {
+				toAccount = PortfolioAccountData.lookup(toSavingsAccountId,
+						toSavingsAccountNo);
+				toAccountType = AccountTransferEnumerations
+						.accountType(PortfolioAccountType.SAVINGS);
+			} else if (toLoanAccountId != null) {
+				toAccount = PortfolioAccountData.lookup(toLoanAccountId,
+						toLoanAccountNo);
+				toAccountType = AccountTransferEnumerations
+						.accountType(PortfolioAccountType.LOAN);
+			}
+
+			return AccountTransferData.instance(id, reversed, transferDate,
+					currency, transferAmount, transferDescription, fromOffice,
+					toOffice, fromClient, toClient, fromAccountType,
+					fromAccount, toAccountType, toAccount);
+		}
+	}
+
+	@Override
+	public boolean isAccountTransfer(final Long transactionId,
+			final PortfolioAccountType accountType) {
+		final StringBuilder sql = new StringBuilder(
+				"select count(*) from m_account_transfer_transaction at where ");
+		if (accountType.isLoanAccount()) {
+			sql.append("at.from_loan_transaction_id=").append(transactionId)
+					.append(" or at.to_loan_transaction_id=")
+					.append(transactionId);
+		} else {
+			sql.append("at.from_savings_transaction_id=").append(transactionId)
+					.append(" or at.to_savings_transaction_id=")
+					.append(transactionId);
+		}
+
+		@SuppressWarnings("deprecation")
+		final int count = this.jdbcTemplate.queryForInt(sql.toString());
+		return count > 0;
+	}
+
+	@Override
+	public Page<AccountTransferData> retrieveByStandingInstruction(
+			final Long id, final SearchParameters searchParameters) {
+
+		final StringBuilder sqlBuilder = new StringBuilder(200);
+		sqlBuilder.append("select SQL_CALC_FOUND_ROWS ");
+		sqlBuilder
+				.append(this.accountTransfersMapper.schema())
+				.append(" join m_account_transfer_standing_instructions atsi on atsi.account_transfer_details_id = att.account_transfer_details_id ");
+		sqlBuilder.append(" where atsi.id = ?");
+
+		if (searchParameters != null) {
+			if (searchParameters.isOrderByRequested()) {
+				sqlBuilder.append(" order by ").append(
+						searchParameters.getOrderBy());
+
+				if (searchParameters.isSortOrderProvided()) {
+					sqlBuilder.append(' ').append(
+							searchParameters.getSortOrder());
+				}
+			}
+
+			if (searchParameters.isLimited()) {
+				sqlBuilder.append(" limit ")
+						.append(searchParameters.getLimit());
+				if (searchParameters.isOffset()) {
+					sqlBuilder.append(" offset ").append(
+							searchParameters.getOffset());
+				}
+			}
+		}
+
+		final Object[] finalObjectArray = { id };
+		final String sqlCountRows = "SELECT FOUND_ROWS()";
+		return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlCountRows,
+				sqlBuilder.toString(), finalObjectArray,
+				this.accountTransfersMapper);
+	}
+
+	@Override
+	public AccountTransferData retrieveRefundByTransferTemplate(
+			final Long fromOfficeId, final Long fromClientId,
+			final Long fromAccountId, final Integer fromAccountType,
+			final Long toOfficeId, final Long toClientId,
+			final Long toAccountId, final Integer toAccountType) {
+		// TODO Auto-generated method stub
+		final EnumOptionData loanAccountType = AccountTransferEnumerations
+				.accountType(PortfolioAccountType.LOAN);
+		final EnumOptionData savingsAccountType = AccountTransferEnumerations
+				.accountType(PortfolioAccountType.SAVINGS);
+
+		final Integer mostRelevantFromAccountType = fromAccountType;
+		final Collection<EnumOptionData> fromAccountTypeOptions = Arrays
+				.asList(savingsAccountType, loanAccountType);
+		final Collection<EnumOptionData> toAccountTypeOptions;
+		if (mostRelevantFromAccountType == 1) {
+			// overpaid loan amt transfer to savings account
+			toAccountTypeOptions = Arrays.asList(savingsAccountType);
+		} else {
+			toAccountTypeOptions = Arrays.asList(loanAccountType,
+					savingsAccountType);
+		}
+		final Integer mostRelevantToAccountType = toAccountType;
+
+		final EnumOptionData fromAccountTypeData = AccountTransferEnumerations
+				.accountType(mostRelevantFromAccountType);
+		final EnumOptionData toAccountTypeData = AccountTransferEnumerations
+				.accountType(mostRelevantToAccountType);
+
+		// from settings
+		OfficeData fromOffice = null;
+		ClientData fromClient = null;
+		PortfolioAccountData fromAccount = null;
+
+		OfficeData toOffice = null;
+		ClientData toClient = null;
+		PortfolioAccountData toAccount = null;
+
+		// template
+		Collection<PortfolioAccountData> fromAccountOptions = null;
+		Collection<PortfolioAccountData> toAccountOptions = null;
+
+		Long mostRelevantFromOfficeId = fromOfficeId;
+		Long mostRelevantFromClientId = fromClientId;
+
+		Long mostRelevantToOfficeId = toOfficeId;
+		Long mostRelevantToClientId = toClientId;
+
+		if (fromAccountId != null) {
+			Integer accountType;
+			if (mostRelevantFromAccountType == 1) {
+				accountType = PortfolioAccountType.LOAN.getValue();
+			} else {
+				accountType = PortfolioAccountType.SAVINGS.getValue();
+			}
+			fromAccount = this.portfolioAccountReadPlatformService
+					.retrieveOneByPaidInAdvance(fromAccountId, accountType);
+
+			// override provided fromClient with client of account
+			mostRelevantFromClientId = fromAccount.clientId();
+		}
+
+		if (mostRelevantFromClientId != null) {
+			fromClient = this.clientReadPlatformService
+					.retrieveOne(mostRelevantFromClientId);
+			mostRelevantFromOfficeId = fromClient.officeId();
+			long[] loanStatus = null;
+			if (mostRelevantFromAccountType == 1) {
+				loanStatus = new long[] { 300, 700 };
+			}
+			PortfolioAccountDTO portfolioAccountDTO = new PortfolioAccountDTO(
+					mostRelevantFromAccountType, mostRelevantFromClientId,
+					loanStatus);
+			fromAccountOptions = this.portfolioAccountReadPlatformService
+					.retrieveAllForLookup(portfolioAccountDTO);
+		}
+
+		Collection<OfficeData> fromOfficeOptions = null;
+		Collection<ClientData> fromClientOptions = null;
+		if (mostRelevantFromOfficeId != null) {
+			fromOffice = this.officeReadPlatformService
+					.retrieveOffice(mostRelevantFromOfficeId);
+			fromOfficeOptions = this.officeReadPlatformService
+					.retrieveAllOfficesForDropdown();
+			fromClientOptions = this.clientReadPlatformService
+					.retrieveAllForLookupByOfficeId(mostRelevantFromOfficeId);
+		}
+
+		// defaults
+		final LocalDate transferDate = DateUtils.getLocalDateOfTenant();
+		Collection<OfficeData> toOfficeOptions = fromOfficeOptions;
+		Collection<ClientData> toClientOptions = null;
+
+		if (toAccountId != null && fromAccount != null) {
+			toAccount = this.portfolioAccountReadPlatformService.retrieveOne(
+					toAccountId, mostRelevantToAccountType,
+					fromAccount.currencyCode());
+			mostRelevantToClientId = toAccount.clientId();
+		}
+
+		if (mostRelevantToClientId != null) {
+			toClient = this.clientReadPlatformService
+					.retrieveOne(mostRelevantToClientId);
+			mostRelevantToOfficeId = toClient.officeId();
+
+			toClientOptions = this.clientReadPlatformService
+					.retrieveAllForLookupByOfficeId(mostRelevantToOfficeId);
+
+			toAccountOptions = retrieveToAccounts(fromAccount,
+					mostRelevantToAccountType, mostRelevantToClientId);
+		}
+
+		if (mostRelevantToOfficeId != null) {
+			toOffice = this.officeReadPlatformService
+					.retrieveOffice(mostRelevantToOfficeId);
+			toOfficeOptions = this.officeReadPlatformService
+					.retrieveAllOfficesForDropdown();
+
+			toClientOptions = this.clientReadPlatformService
+					.retrieveAllForLookupByOfficeId(mostRelevantToOfficeId);
+			if (toClientOptions != null && toClientOptions.size() == 1) {
+				toClient = new ArrayList<>(toClientOptions).get(0);
+
+				toAccountOptions = retrieveToAccounts(fromAccount,
+						mostRelevantToAccountType, mostRelevantToClientId);
+			}
+		}
+
+		return AccountTransferData.template(fromOffice, fromClient,
+				fromAccountTypeData, fromAccount, transferDate, toOffice,
+				toClient, toAccountTypeData, toAccount, fromOfficeOptions,
+				fromClientOptions, fromAccountTypeOptions, fromAccountOptions,
+				toOfficeOptions, toClientOptions, toAccountTypeOptions,
+				toAccountOptions);
+	}
+
+	@Override
+	public BigDecimal getTotalTransactionAmount(Long accountId,
+			Integer accountType, LocalDate transactionDate) {
+		StringBuilder sqlBuilder = new StringBuilder(
+				" select sum(trans.amount) as totalTransactionAmount ");
+		sqlBuilder.append(" from m_account_transfer_details as det ");
+		sqlBuilder
+				.append(" inner join m_account_transfer_transaction as trans ");
+		sqlBuilder.append(" on det.id = trans.account_transfer_details_id ");
+		sqlBuilder.append(" where trans.is_reversed = 0 ");
+		sqlBuilder.append(" and trans.transaction_date = ? ");
+		sqlBuilder
+				.append(" and IF(1=?, det.from_loan_account_id = ?, det.from_savings_account_id = ?) ");
+
+		return this.jdbcTemplate.queryForObject(sqlBuilder.toString(),
+				new Object[] { this.formatter.print(transactionDate),
+						accountType, accountId, accountId }, BigDecimal.class);
+	}
 
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/2bd3b062/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepository.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepository.java
index 845cc67..b306d70 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepository.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepository.java
@@ -69,6 +69,8 @@ public interface LoanRepository extends JpaRepository<Loan, Long>, JpaSpecificat
 
     public static final String DOES_PRODUCT_HAVE_NON_CLOSED_LOANS = "select case when (count (loan) > 0) then true else false end from Loan loan where loan.loanProduct.id = :productId and loan.loanStatus in (100,200,300,303,304,700)";
 
+    public static final String FIND_BY_ACCOUNT_NUMBER = "from Loan loan where loan.accountNumber = :accountNumber and loan.loanStatus in (100,200,300,303,304)";
+
     @Query(FIND_GROUP_LOANS_DISBURSED_AFTER)
     List<Loan> getGroupLoansDisbursedAfter(@Param("disbursementDate") Date disbursementDate, @Param("groupId") Long groupId,
             @Param("loanType") Integer loanType);
@@ -144,5 +146,7 @@ public interface LoanRepository extends JpaRepository<Loan, Long>, JpaSpecificat
     
     @Query(DOES_PRODUCT_HAVE_NON_CLOSED_LOANS)
     boolean doNonClosedLoanAccountsExistForProduct(@Param("productId") Long productId);
-
+    
+    @Query(FIND_BY_ACCOUNT_NUMBER)
+    Loan findNonClosedLoanByAccountNumber(@Param("accountNumber") String accountNumber);
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/2bd3b062/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountRepository.java
----------------------------------------------------------------------
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountRepository.java
index b34e897..b614b25 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountRepository.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountRepository.java
@@ -45,4 +45,7 @@ public interface SavingsAccountRepository extends JpaRepository<SavingsAccount,
     @Query("from SavingsAccount sa where sa.id = :accountId and sa.depositType = :depositAccountTypeId")
     SavingsAccount findByIdAndDepositAccountType(@Param("accountId") Long accountId,
             @Param("depositAccountTypeId") Integer depositAccountTypeId);
+
+    @Query("from SavingsAccount sa where sa.accountNumber = :accountNumber and sa.status in (100, 200, 300, 303, 304) ")
+    SavingsAccount findNonClosedAccountByAccountNumber(@Param("accountNumber") String accountNumber);
 }
\ No newline at end of file