Merge branch 'PR378' into develop
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java
index b279163..1ae2609 100755
--- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java
@@ -28,10 +28,12 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Calendar;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
 
+import org.apache.fineract.infrastructure.core.service.DateUtils;
 import org.apache.fineract.integrationtests.common.ClientHelper;
 import org.apache.fineract.integrationtests.common.CommonConstants;
 import org.apache.fineract.integrationtests.common.SchedulerJobHelper;
@@ -2214,13 +2216,17 @@
         Integer holdTransactionId = (Integer) this.savingsAccountHelper.holdAmountInSavingsAccount(savingsId, String.valueOf(balance - 100),
                 SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID);
         error = (List) savingsAccountHelperValidationError.withdrawalFromSavingsAccount(savingsId, "300",
-                SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_ERROR);
+                SavingsAccountHelper.TRANSACTION_DATE_PLUS_ONE, CommonConstants.RESPONSE_ERROR);
         assertEquals("error.msg.savingsaccount.transaction.insufficient.account.balance",
                 error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
 
         Integer releaseTransactionId = this.savingsAccountHelper.releaseAmount(savingsId, holdTransactionId);
-        withdrawTransactionId = (Integer) this.savingsAccountHelper.withdrawalFromSavingsAccount(savingsId, "300",
-                SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID);
+        Date today = DateUtils.getDateOfTenant();
+        String todayDate = today.toString();
+        SimpleDateFormat dt1 = new SimpleDateFormat("dd MMM yyyy");
+        todayDate = dt1.format(today).toString();
+        withdrawTransactionId = (Integer) this.savingsAccountHelper.withdrawalFromSavingsAccount(savingsId, "300", todayDate,
+                CommonConstants.RESPONSE_RESOURCE_ID);
         withdrawTransaction = this.savingsAccountHelper.getSavingsTransaction(savingsId, withdrawTransactionId);
         balance -= new Float("300");
         assertEquals("Verifying Withdrawal Amount", new Float("300"), withdrawTransaction.get("amount"));
diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java
index c2cd4f9..d0752a3 100644
--- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java
+++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java
@@ -69,6 +69,7 @@
     public static final String CREATED_DATE_PLUS_ONE = "09 January 2013";
     public static final String CREATED_DATE_MINUS_ONE = "07 January 2013";
     public static final String TRANSACTION_DATE = "01 March 2013";
+    public static final String TRANSACTION_DATE_PLUS_ONE = "02 March 2013";
     public static final String LAST_TRANSACTION_DATE = "01 March 2013";
     public static final String ACCOUNT_TYPE_INDIVIDUAL = "INDIVIDUAL";
 
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/accrual/api/AccrualAccountingConstants.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/accrual/api/AccrualAccountingConstants.java
index 000b3e4..5ed32c3 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/accounting/accrual/api/AccrualAccountingConstants.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/accrual/api/AccrualAccountingConstants.java
@@ -30,7 +30,4 @@
     
     public static final String PERIODIC_ACCRUAL_ACCOUNTING_RESOURCE_NAME = "periodicaccrual";
     public static final String PERIODIC_ACCRUAL_ACCOUNTING_EXECUTION_ERROR_CODE = "execution.failed";
-
-    public static final Set<String> LOAN_PERIODIC_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(accrueTillParamName,
-            localeParamName, dateFormatParamName));
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/accrual/serialization/AccrualAccountingDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/accrual/serialization/AccrualAccountingDataValidator.java
index e3f48c7..f024c1c 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/accounting/accrual/serialization/AccrualAccountingDataValidator.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/accrual/serialization/AccrualAccountingDataValidator.java
@@ -18,14 +18,16 @@
  */
 package org.apache.fineract.accounting.accrual.serialization;
 
-import static org.apache.fineract.accounting.accrual.api.AccrualAccountingConstants.LOAN_PERIODIC_REQUEST_DATA_PARAMETERS;
 import static org.apache.fineract.accounting.accrual.api.AccrualAccountingConstants.PERIODIC_ACCRUAL_ACCOUNTING_RESOURCE_NAME;
 import static org.apache.fineract.accounting.accrual.api.AccrualAccountingConstants.accrueTillParamName;
 
 import java.lang.reflect.Type;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import org.apache.commons.lang.StringUtils;
 import org.apache.fineract.accounting.accrual.api.AccrualAccountingConstants;
@@ -52,6 +54,9 @@
 public final class AccrualAccountingDataValidator {
 
     private final FromJsonHelper fromApiJsonHelper;
+	private static final Set<String> LOAN_PERIODIC_REQUEST_DATA_PARAMETERS = new HashSet<>(
+			Arrays.asList(accrueTillParamName, AccrualAccountingConstants.localeParamName,
+					AccrualAccountingConstants.dateFormatParamName));
 
     @Autowired
     public AccrualAccountingDataValidator(final FromJsonHelper fromApiJsonfromApiJsonHelper) {
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/exception/JournalEntryRuntimeException.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/exception/JournalEntryRuntimeException.java
new file mode 100644
index 0000000..a0fa0a7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/exception/JournalEntryRuntimeException.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.accounting.journalentry.exception;
+
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class JournalEntryRuntimeException extends AbstractPlatformDomainRuleException {
+
+    public JournalEntryRuntimeException(String errorCode, String errorMessage) {
+        super(errorCode, errorMessage);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java
index b556014..ad18582 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java
@@ -52,6 +52,8 @@
 import org.apache.fineract.accounting.journalentry.exception.JournalEntriesNotFoundException;
 import org.apache.fineract.accounting.journalentry.exception.JournalEntryInvalidException;
 import org.apache.fineract.accounting.journalentry.exception.JournalEntryInvalidException.GL_JOURNAL_ENTRY_INVALID_REASON;
+
+import org.apache.fineract.accounting.journalentry.exception.JournalEntryRuntimeException;
 import org.apache.fineract.accounting.journalentry.serialization.JournalEntryCommandFromApiJsonDeserializer;
 import org.apache.fineract.accounting.producttoaccountmapping.domain.PortfolioProductType;
 import org.apache.fineract.accounting.provisioning.domain.LoanProductProvisioningEntry;
@@ -255,7 +257,9 @@
                     }
                 }
             }
-            if (credits.length != validCredits.length) { throw new RuntimeException("Invalid credits"); }
+			if (credits.length != validCredits.length) {
+				throw new JournalEntryRuntimeException("error.msg.glJournalEntry.invalid.credits", "Invalid Credits.");
+			}
         }
 
         if (debits != null && debits.length > 0) {
@@ -268,9 +272,11 @@
                         validDebits[i] = debit;
                     }
                 }
-            }
-            if (debits.length != validDebits.length) { throw new RuntimeException("Invalid debits"); }
-        }
+			}
+			if (debits.length != validDebits.length) {
+				throw new JournalEntryRuntimeException("error.msg.glJournalEntry.invalid.debits","Invalid Debits");
+			}
+		}
     }
 
     private void checkDebitAndCreditAmounts(final SingleDebitOrCreditEntryCommand[] credits, final SingleDebitOrCreditEntryCommand[] debits) {
diff --git a/fineract-provider/src/main/java/org/apache/fineract/adhocquery/api/AdHocApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/api/AdHocApiResource.java
new file mode 100644
index 0000000..75380f1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/api/AdHocApiResource.java
@@ -0,0 +1,170 @@
+/**
+ * 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.adhocquery.api;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+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.commons.lang.StringUtils;
+import org.apache.fineract.adhocquery.data.AdHocData;
+import org.apache.fineract.adhocquery.service.AdHocReadPlatformService;
+import org.apache.fineract.commands.domain.CommandWrapper;
+import org.apache.fineract.commands.service.CommandWrapperBuilder;
+import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
+import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/adhocquery")
+@Component
+@Scope("singleton")
+public class AdHocApiResource {
+
+    /**
+     * The set of parameters that are supported in response for {@link AdhocData}
+     */ 
+    private final Set<String> RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList("id", "name", "query", "tableName","tableField","isActive","createdBy","createdOn","createdById","updatedById","updatedOn","email"));
+    
+    private final PlatformSecurityContext context;
+    private final AdHocReadPlatformService adHocReadPlatformService;
+    private final DefaultToApiJsonSerializer<AdHocData> toApiJsonSerializer;
+    private final ApiRequestParameterHelper apiRequestParameterHelper;
+    private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+
+    @Autowired
+    public AdHocApiResource(final PlatformSecurityContext context, final AdHocReadPlatformService readPlatformService,
+            final DefaultToApiJsonSerializer<AdHocData> toApiJsonSerializer,
+            final ApiRequestParameterHelper apiRequestParameterHelper,
+            final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) {
+        this.context = context;
+        this.adHocReadPlatformService = readPlatformService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+        this.apiRequestParameterHelper = apiRequestParameterHelper;
+        this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
+    }
+
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAll(@Context final UriInfo uriInfo) {
+    	
+        this.context.authenticatedUser();
+        final Collection<AdHocData> adhocs = this.adHocReadPlatformService.retrieveAllAdHocQuery();
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, adhocs, this.RESPONSE_DATA_PARAMETERS);
+    }
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    @Path("template")
+    public String template(@Context final UriInfo uriInfo) {
+        this.context.authenticatedUser();
+        final AdHocData user = this.adHocReadPlatformService.retrieveNewAdHocDetails();
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, user, this.RESPONSE_DATA_PARAMETERS);
+    }
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String createAdHocQuery(final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+                .createAdHoc() //
+                .withJson(apiRequestBodyAsJson) //
+                .build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    @GET
+    @Path("{adHocId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String retrieveAdHocQuery(@PathParam("adHocId") final Long adHocId, @Context final UriInfo uriInfo) {
+
+        this.context.authenticatedUser();
+
+        final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+
+        final AdHocData adhoc = this.adHocReadPlatformService.retrieveOne(adHocId);
+
+        return this.toApiJsonSerializer.serialize(settings, adhoc, this.RESPONSE_DATA_PARAMETERS);
+    }
+    @PUT
+    @Path("{adHocId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String update(@PathParam("adHocId") final Long adHocId, final String apiRequestBodyAsJson) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+                .updateAdHoc(adHocId) //
+                .withJson(apiRequestBodyAsJson) //
+                .build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+     /**
+     * Delete AdHocQuery
+     * 
+     * @param adHocId
+     * @return
+     */
+    @DELETE
+    @Path("{adHocId}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String deleteAdHocQuery(@PathParam("adHocId") final Long adHocId) {
+
+        final CommandWrapper commandRequest = new CommandWrapperBuilder() //
+                .deleteAdHoc(adHocId) //
+                .build();
+
+        final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
+
+        return this.toApiJsonSerializer.serialize(result);
+    }
+
+    private boolean is(final String commandParam, final String commandValue) {
+        return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue);
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/adhocquery/api/AdHocJsonInputParams.java b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/api/AdHocJsonInputParams.java
new file mode 100644
index 0000000..0cb3384
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/api/AdHocJsonInputParams.java
@@ -0,0 +1,55 @@
+/**
+ * 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.adhocquery.api;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/***
+ * Enum of all parameters passed in while creating/updating a AdHocQuery 
+ ***/
+public enum AdHocJsonInputParams {
+    ID("id"), NAME("name"),QUERY("query"),TABLENAME("tableName"),TABLEFIELD("tableFields"), ISACTIVE("isActive"),EMAIL("email");
+
+    private final String value;
+
+    private AdHocJsonInputParams(final String value) {
+        this.value = value;
+    }
+
+    private static final Set<String> values = new HashSet<>();
+    static {
+        for (final AdHocJsonInputParams type : AdHocJsonInputParams.values()) {
+            values.add(type.value);
+        }
+    }
+
+    public static Set<String> getAllValues() {
+        return values;
+    }
+
+    @Override
+    public String toString() {
+        return name().toString().replaceAll("_", " ");
+    }
+
+    public String getValue() {
+        return this.value;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/adhocquery/data/AdHocData.java b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/data/AdHocData.java
new file mode 100644
index 0000000..f0fd7a8
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/data/AdHocData.java
@@ -0,0 +1,122 @@
+/**
+ * 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.adhocquery.data;
+
+import java.util.Collection;
+
+import org.apache.fineract.organisation.office.data.OfficeData;
+import org.apache.fineract.useradministration.data.AppUserData;
+import org.apache.fineract.useradministration.data.RoleData;
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+
+/**
+ * Immutable data object represent note or case information AdHocData
+ * 
+ */
+public class AdHocData {
+
+	
+    
+	@SuppressWarnings("unused")
+    private final Long id;
+    @SuppressWarnings("unused")
+    private final String name;
+	@SuppressWarnings("unused")
+    private final String query;
+	@SuppressWarnings("unused")
+    private final String tableName;
+	@SuppressWarnings("unused")
+    private final String tableFields;
+	@SuppressWarnings("unused")
+	private final String email;
+	@SuppressWarnings("unused")
+    private final boolean isActive;
+	@SuppressWarnings("unused")
+    private final DateTime createdOn;
+	@SuppressWarnings("unused")
+	private final Long createdById;
+	@SuppressWarnings("unused")
+    private final Long updatedById;   
+	@SuppressWarnings("unused")
+    private final DateTime updatedOn;
+	@SuppressWarnings("unused")
+	private final String createdBy;
+	
+    
+	
+
+    public AdHocData(final Long id, final String name,final String query, final String tableName,final String tableFields, 
+    		final boolean isActive, final DateTime createdOn, final Long createdById,final Long updatedById,
+    		final DateTime updatedOn,final String createdBy,final String email
+            ) {
+        this.id = id;
+        this.name=name;
+        this.query=query;
+        this.tableName = tableName;
+        this.tableFields = tableFields;
+        this.isActive = isActive;
+        this.createdOn = createdOn;
+        this.createdById = createdById;
+        this.updatedById=updatedById;
+        this.updatedOn=updatedOn;
+        this.createdBy=createdBy;
+        this.email=email;
+    }
+    public static AdHocData template() {
+        AdHocData adHocData = new AdHocData(null,null,null,null,null,false,null,null,null,null,null,null);
+		return adHocData;
+    }
+    public Long getId() {
+		return this.id;
+	}
+	public String getName() {
+		return this.name;
+	}
+	public String getQuery() {
+		return this.query;
+	}
+	public String getTableName() {
+		return this.tableName;
+	}
+	public String getTableFields() {
+		return this.tableFields;
+	}
+	public String getEmail() {
+		return this.email;
+	}
+	public boolean isActive() {
+		return this.isActive;
+	}
+	public DateTime getCreatedOn() {
+		return this.createdOn;
+	}
+	public Long getCreatedById() {
+		return this.createdById;
+	}
+	public Long getUpdatedById() {
+		return this.updatedById;
+	}
+	public DateTime getUpdatedOn() {
+		return this.updatedOn;
+	}
+	public String getCreatedBy() {
+		return this.createdBy;
+	}
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/adhocquery/domain/AdHoc.java b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/domain/AdHoc.java
new file mode 100644
index 0000000..be315ba
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/domain/AdHoc.java
@@ -0,0 +1,153 @@
+/**
+ * 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.adhocquery.domain;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+import org.apache.commons.lang.BooleanUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.fineract.adhocquery.api.AdHocJsonInputParams;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
+import org.apache.fineract.infrastructure.security.utils.SQLInjectionValidator;
+import org.apache.fineract.useradministration.domain.AppUser;
+
+
+@Entity
+@Table(name = "m_adhoc")
+public class AdHoc extends AbstractAuditableCustom<AppUser, Long> {
+
+  	@Column(name = "name", length = 100)
+    private  String name;
+    
+    @Column(name = "query", length = 2000)
+    private  String query;
+   	
+    @Column(name = "table_name", length = 100)
+    private  String tableName;
+    
+    
+    @Column(name = "table_fields", length = 2000)
+    private  String tableFields;
+   	
+    @Column(name = "email", length = 500)
+    private  String email;
+    
+   
+	@Column(name = "IsActive", nullable = false)
+    private boolean isActive = false;
+   	
+    private AdHoc(final String name, final String query,final String tableName,final String tableFields ,final String email,final boolean isActive) {
+        this.name = StringUtils.defaultIfEmpty(name, null);
+        this.query=StringUtils.defaultIfEmpty(query,null);
+        this.tableName=StringUtils.defaultIfEmpty(tableName,null);
+        this.tableFields=StringUtils.defaultIfEmpty(tableFields,null);
+        this.email=StringUtils.defaultIfEmpty(email,null);
+        this.isActive = BooleanUtils.toBooleanDefaultIfNull(isActive, false);
+       
+    }
+    public static AdHoc fromJson(final JsonCommand command) {
+        final String name = command.stringValueOfParameterNamed(AdHocJsonInputParams.NAME.getValue());
+        
+        String commandQuery=command.stringValueOfParameterNamed(AdHocJsonInputParams.QUERY.getValue());
+        
+        SQLInjectionValidator.validateAdhocQuery(commandQuery);
+        final String query = commandQuery;
+        final String tableName = command.stringValueOfParameterNamed(AdHocJsonInputParams.TABLENAME.getValue());
+        final String tableFields = command.stringValueOfParameterNamed(AdHocJsonInputParams.TABLEFIELD.getValue());
+        final String email = command.stringValueOfParameterNamed(AdHocJsonInputParams.EMAIL.getValue());
+        final boolean isActive = command.booleanPrimitiveValueOfParameterNamed(AdHocJsonInputParams.ISACTIVE.getValue());
+        return new AdHoc(name,query,tableName,tableFields ,email,isActive);
+    }
+    
+    public Map<String, Object> update(final JsonCommand command) {
+
+        final Map<String, Object> actualChanges = new LinkedHashMap<>(7);
+
+        final String nameParamName = "name";
+        if (command.isChangeInStringParameterNamed(nameParamName, this.name)) {
+            final String newValue = command.stringValueOfParameterNamed(nameParamName);
+            actualChanges.put(nameParamName, newValue);
+            this.name = newValue;
+        }
+
+        final String descriptionParamName = "query";
+        if (command.isChangeInStringParameterNamed(descriptionParamName, this.query)) {
+            final String newValue = command.stringValueOfParameterNamed(descriptionParamName);
+            actualChanges.put(descriptionParamName, newValue);
+            this.query = newValue;
+        }
+        final String tableName = "tableName";
+        if (command.isChangeInStringParameterNamed(tableName, this.tableName)) {
+            final String newValue = command.stringValueOfParameterNamed(tableName);
+            actualChanges.put(tableName, newValue);
+            this.tableName = newValue;
+        }
+        final String tableField = "tableField";
+        if (command.isChangeInStringParameterNamed(tableField, this.tableFields)) {
+            final String newValue = command.stringValueOfParameterNamed(tableField);
+            actualChanges.put(tableField, newValue);
+            this.tableFields = newValue;
+        }
+        final String email = "email";
+        if (command.isChangeInStringParameterNamed(email, this.email)) {
+            final String newValue = command.stringValueOfParameterNamed(email);
+            actualChanges.put(email, newValue);
+            this.email = newValue;
+        }
+        final String paramisActive = "isActive";
+        if (command.isChangeInBooleanParameterNamed(paramisActive, this.isActive)) {
+        	final Boolean newValue = command.booleanObjectValueOfParameterNamed(paramisActive);
+            actualChanges.put(paramisActive, newValue);
+            this.isActive = newValue;
+        }
+        return actualChanges;
+    }
+    
+    public String getName() {
+		return name;
+	}
+	public String getQuery() {
+		return query;
+	}
+	public String getTableName() {
+		return tableName;
+	}
+	public String getTableFields() {
+		return tableFields;
+	}
+    public boolean isActive() {
+        return this.isActive;
+    }
+	public String getEmail() {
+		return email;
+	}
+    public void disableActive() {
+        this.isActive = true;
+    }
+    public void enableActive() {
+    	this.isActive = false;
+    }
+
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/adhocquery/domain/AdHocRepository.java b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/domain/AdHocRepository.java
new file mode 100644
index 0000000..fc31eb7
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/domain/AdHocRepository.java
@@ -0,0 +1,26 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.adhocquery.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface AdHocRepository extends JpaRepository<AdHoc, Long>, JpaSpecificationExecutor<AdHoc> {
+    // no added behaviour
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/adhocquery/exception/AdHocNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/exception/AdHocNotFoundException.java
new file mode 100644
index 0000000..7de67f5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/exception/AdHocNotFoundException.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.adhocquery.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+/**
+ * A {@link RuntimeException} thrown when AdHoc  resources are not
+ * found.
+ */
+public class AdHocNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public AdHocNotFoundException(final Long id) {
+        super("error.msg.adhocquery.adhoc.id.invalid", "Adhoc Record with identifier " + id + " does not exist", id);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/adhocquery/handler/CreateAdHocCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/handler/CreateAdHocCommandHandler.java
new file mode 100644
index 0000000..73f09dc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/handler/CreateAdHocCommandHandler.java
@@ -0,0 +1,47 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.adhocquery.handler;
+
+import org.apache.fineract.adhocquery.service.AdHocWritePlatformService;
+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.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "ADHOC", action = "CREATE")
+public class CreateAdHocCommandHandler implements NewCommandSourceHandler {
+
+    private final AdHocWritePlatformService writePlatformService;
+
+    @Autowired
+    public CreateAdHocCommandHandler(final AdHocWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.createAdHocQuery(command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/adhocquery/handler/DeleteAdHocCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/handler/DeleteAdHocCommandHandler.java
new file mode 100644
index 0000000..eb8e9f6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/handler/DeleteAdHocCommandHandler.java
@@ -0,0 +1,47 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.adhocquery.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.adhocquery.service.AdHocWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "ADHOC", action = "DELETE")
+public class DeleteAdHocCommandHandler implements NewCommandSourceHandler {
+
+    private final AdHocWritePlatformService writePlatformService;
+
+    @Autowired
+    public DeleteAdHocCommandHandler(final AdHocWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        return this.writePlatformService.deleteAdHocQuery(command.entityId());
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/adhocquery/handler/UpdateAdHocCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/handler/UpdateAdHocCommandHandler.java
new file mode 100644
index 0000000..db9d4ac
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/handler/UpdateAdHocCommandHandler.java
@@ -0,0 +1,48 @@
+/**
+ * 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.adhocquery.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.adhocquery.service.AdHocWritePlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@CommandType(entity = "ADHOC", action = "UPDATE")
+public class UpdateAdHocCommandHandler implements NewCommandSourceHandler {
+
+    private final AdHocWritePlatformService writePlatformService;
+
+    @Autowired
+    public UpdateAdHocCommandHandler(final AdHocWritePlatformService writePlatformService) {
+        this.writePlatformService = writePlatformService;
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        final Long adHocId = command.entityId();
+        return this.writePlatformService.updateAdHocQuery(adHocId, command);
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocDataValidator.java
new file mode 100644
index 0000000..2cdd294
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocDataValidator.java
@@ -0,0 +1,127 @@
+/**
+ * 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.adhocquery.service;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+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.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+
+@Component
+public final class AdHocDataValidator {
+
+    /**
+     * The parameters supported for this command.
+     */
+    private final Set<String> supportedParameters = new HashSet<>(Arrays.asList("name","query","tableName","tableFields","email","isActive"));
+
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Autowired
+    public AdHocDataValidator(final FromJsonHelper fromApiJsonHelper) {
+        this.fromApiJsonHelper = fromApiJsonHelper;
+    }
+
+    public void validateForCreate(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, this.supportedParameters);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("adhoc");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        final String name = this.fromApiJsonHelper.extractStringNamed("name", element);
+        baseDataValidator.reset().parameter("name").value(name).notBlank().notExceedingLengthOf(100);
+
+        final String description = this.fromApiJsonHelper.extractStringNamed("query", element);
+        baseDataValidator.reset().parameter("query").value(description).notBlank().notExceedingLengthOf(2000);
+        
+        final String tableName = this.fromApiJsonHelper.extractStringNamed("tableName", element);
+        baseDataValidator.reset().parameter("tableName").value(tableName).notBlank().notExceedingLengthOf(100);
+        
+        final String tableFields = this.fromApiJsonHelper.extractStringNamed("tableFields", element);
+        baseDataValidator.reset().parameter("tableFields").value(tableFields).notBlank().notExceedingLengthOf(1000);
+
+        final String email = this.fromApiJsonHelper.extractStringNamed("email", element);
+        baseDataValidator.reset().parameter("email").value(email).notBlank().notExceedingLengthOf(500);
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    public void validateForUpdate(final String json) {
+        if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
+
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, this.supportedParameters);
+
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("adhoc");
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        if (this.fromApiJsonHelper.parameterExists("name", element)) {
+            final String name = this.fromApiJsonHelper.extractStringNamed("name", element);
+            baseDataValidator.reset().parameter("name").value(name).notBlank().notExceedingLengthOf(100);
+        }
+
+        if (this.fromApiJsonHelper.parameterExists("query", element)) {
+            final String query = this.fromApiJsonHelper.extractStringNamed("query", element);
+            baseDataValidator.reset().parameter("query").value(query).notBlank().notExceedingLengthOf(2000);
+        }
+        if (this.fromApiJsonHelper.parameterExists("tableName", element)) {
+            final String tableName = this.fromApiJsonHelper.extractStringNamed("tableName", element);
+            baseDataValidator.reset().parameter("tableName").value(tableName).notBlank().notExceedingLengthOf(100);
+        }
+        if (this.fromApiJsonHelper.parameterExists("tableFields", element)) {
+            final String tableField = this.fromApiJsonHelper.extractStringNamed("tableFields", element);
+            baseDataValidator.reset().parameter("tableFields").value(tableField).notBlank().notExceedingLengthOf(2000);
+        }
+        if (this.fromApiJsonHelper.parameterExists("email", element)) {
+            final String email = this.fromApiJsonHelper.extractStringNamed("email", element);
+            baseDataValidator.reset().parameter("email").value(email).notBlank().notExceedingLengthOf(500);
+        }
+        /*if (this.fromApiJsonHelper.parameterExists("isActive", element)) {
+            final Integer isActive = this.fromApiJsonHelper.extractIntegerNamed("isActive", element, Locale.getDefault());
+            baseDataValidator.reset().parameter("isActive").value(isActive).notNull().inMinMaxRange(1, 2);
+        }*/
+        
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    }
+
+    private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocReadPlatformService.java
new file mode 100644
index 0000000..414a2b2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocReadPlatformService.java
@@ -0,0 +1,34 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.adhocquery.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.adhocquery.data.AdHocData;
+
+public interface AdHocReadPlatformService {
+
+    Collection<AdHocData> retrieveAllAdHocQuery();
+
+    Collection<AdHocData> retrieveAllActiveAdHocQuery();
+
+    AdHocData retrieveOne(Long adHocId);
+    AdHocData retrieveNewAdHocDetails();
+    
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocReadPlatformServiceImpl.java
new file mode 100644
index 0000000..eb1f633
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocReadPlatformServiceImpl.java
@@ -0,0 +1,109 @@
+/**
+ * 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.adhocquery.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+
+import org.apache.fineract.adhocquery.data.AdHocData;
+import org.apache.fineract.adhocquery.exception.AdHocNotFoundException;
+import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.joda.time.DateTime;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+
+@Service
+public class AdHocReadPlatformServiceImpl implements AdHocReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final AdHocMapper adHocRowMapper;
+
+    @Autowired
+    public AdHocReadPlatformServiceImpl(final RoutingDataSource dataSource) {
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.adHocRowMapper = new AdHocMapper();
+    }
+
+    @Override
+    public Collection<AdHocData> retrieveAllAdHocQuery() {
+        final String sql = "select " + this.adHocRowMapper.schema() + " order by r.id";
+
+        return this.jdbcTemplate.query(sql, this.adHocRowMapper);
+    }
+
+    @Override
+    public Collection<AdHocData> retrieveAllActiveAdHocQuery() {
+        final String sql = "select " + this.adHocRowMapper.schema() + " where r.IsActive = 1 order by r.id";
+
+        return this.jdbcTemplate.query(sql, this.adHocRowMapper);
+    }
+
+    @Override
+    public AdHocData retrieveOne(final Long id) {
+
+        try {
+            final String sql = "select " + this.adHocRowMapper.schema() + " where r.id=?";
+
+            return this.jdbcTemplate.queryForObject(sql, this.adHocRowMapper, new Object[] { id });
+        } catch (final EmptyResultDataAccessException e) {
+            throw new AdHocNotFoundException(id);
+        }
+    }
+
+    protected static final class AdHocMapper implements RowMapper<AdHocData> {
+
+        @Override
+        public AdHocData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
+
+            final Long id = JdbcSupport.getLong(rs, "id");
+            final String name = rs.getString("name");
+            final String query = rs.getString("query");
+            final String tableName=rs.getString("tableName");
+            final String tableFields=rs.getString("tableField");
+            final Boolean isActive = rs.getBoolean("isActive");
+            final DateTime createdDate = JdbcSupport.getDateTime(rs, "createdDate");
+            final Long createdById = JdbcSupport.getLong(rs, "createdById");
+            final Long updatedById=JdbcSupport.getLong(rs, "updatedById");
+            final DateTime updatedOn=JdbcSupport.getDateTime(rs, "updatedOn");
+            final String createdByUsername=rs.getString("createdBy");
+            final String email=rs.getString("email");
+          
+            return new AdHocData(id,name,query, tableName,tableFields,isActive,createdDate,createdById,updatedById,updatedOn,createdByUsername,email);
+        }
+
+        public String schema() {
+            return " r.id as id, r.name as name, r.query as query, r.table_name as tableName,r.table_fields as tableField ,r.IsActive as isActive ,r.email as email ,"
+            		+" r.created_date as createdDate, r.createdby_id as createdById,cb.username as createdBy,r.lastmodifiedby_id as updatedById ,r.lastmodified_date as updatedOn "
+                    + " from m_adhoc r left join m_appuser cb on cb.id=r.createdby_id left join m_appuser mb on mb.id=r.lastmodifiedby_id";
+            		        
+        }
+    }
+
+	@Override
+	public AdHocData retrieveNewAdHocDetails() {
+		 return AdHocData.template();
+	}
+
+   
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocScheduledJobRunnerService.java b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocScheduledJobRunnerService.java
new file mode 100644
index 0000000..88e24a2
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocScheduledJobRunnerService.java
@@ -0,0 +1,23 @@
+/**
+ * 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.adhocquery.service;
+
+public interface AdHocScheduledJobRunnerService {
+    void generateClientSchedule();
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocScheduledJobRunnerServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocScheduledJobRunnerServiceImpl.java
new file mode 100644
index 0000000..d10cddd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocScheduledJobRunnerServiceImpl.java
@@ -0,0 +1,77 @@
+/**
+ * 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.adhocquery.service;
+
+import java.util.Collection;
+
+import org.apache.fineract.adhocquery.data.AdHocData;
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import org.apache.fineract.infrastructure.jobs.annotation.CronTarget;
+import org.apache.fineract.infrastructure.jobs.service.JobName;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service(value = "adHocScheduledJobRunnerService")
+public class AdHocScheduledJobRunnerServiceImpl implements AdHocScheduledJobRunnerService {
+
+    private final static Logger logger = LoggerFactory.getLogger(AdHocScheduledJobRunnerServiceImpl.class);
+    private final AdHocReadPlatformService adHocReadPlatformService;
+    private final JdbcTemplate jdbcTemplate;
+    
+    @Autowired
+    public AdHocScheduledJobRunnerServiceImpl(final RoutingDataSource dataSource,
+    		final AdHocReadPlatformService adHocReadPlatformService
+            ) {
+    	this.jdbcTemplate = new JdbcTemplate(dataSource);
+        this.adHocReadPlatformService = adHocReadPlatformService;
+       
+    }
+    @Transactional
+    @Override
+    @CronTarget(jobName = JobName.GENERATE_ADHOCCLIENT_SCEHDULE)
+    public void generateClientSchedule() {
+    	final Collection<AdHocData> adhocs = this.adHocReadPlatformService.retrieveAllActiveAdHocQuery();
+        if(adhocs.size()>0){
+        	adhocs.forEach(adhoc->{
+            	//jdbcTemplate.execute("truncate table "+adhoc.getTableName());
+            	final StringBuilder insertSqlBuilder = new StringBuilder(900);
+                insertSqlBuilder
+                        .append("INSERT INTO ")
+                        .append(adhoc.getTableName()+"(")
+                		.append(adhoc.getTableFields()+") ")
+                		.append(adhoc.getQuery());
+                if (insertSqlBuilder.length() > 0) {
+                	final int result = this.jdbcTemplate.update(insertSqlBuilder.toString());
+                	logger.info(ThreadLocalContextUtil.getTenant().getName() + ": Results affected by inserted: " + result);
+                }
+            });	
+        }else{
+        	logger.info(ThreadLocalContextUtil.getTenant().getName() + "Nothing to update ");
+        }
+        
+        
+   
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocWritePlatformService.java
new file mode 100644
index 0000000..23427fc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocWritePlatformService.java
@@ -0,0 +1,35 @@
+/**
+ * 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.adhocquery.service;
+
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+
+public interface AdHocWritePlatformService {
+
+    CommandProcessingResult createAdHocQuery(JsonCommand command);
+
+    CommandProcessingResult updateAdHocQuery(Long adHocId, JsonCommand command);
+ 
+    CommandProcessingResult deleteAdHocQuery(Long adHocId);
+
+    CommandProcessingResult disableAdHocQuery(Long adHocId);
+
+    CommandProcessingResult enableAdHocQuery(Long adHocId);
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocWritePlatformServiceJpaRepositoryImpl.java
new file mode 100644
index 0000000..1cec38d
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/adhocquery/service/AdHocWritePlatformServiceJpaRepositoryImpl.java
@@ -0,0 +1,193 @@
+/**
+ * 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.adhocquery.service;
+
+import java.util.Map;
+
+import org.apache.fineract.adhocquery.domain.AdHoc;
+import org.apache.fineract.adhocquery.domain.AdHocRepository;
+import org.apache.fineract.adhocquery.exception.AdHocNotFoundException;
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class AdHocWritePlatformServiceJpaRepositoryImpl implements AdHocWritePlatformService {
+
+    private final static Logger logger = LoggerFactory.getLogger(AdHocWritePlatformServiceJpaRepositoryImpl.class);
+    private final PlatformSecurityContext context;
+    private final AdHocRepository adHocRepository;
+    private final AdHocDataValidator adHocCommandFromApiJsonDeserializer;
+   
+
+    @Autowired
+    public AdHocWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final AdHocRepository adHocRepository,
+             final AdHocDataValidator adHocCommandFromApiJsonDeserializer) {
+        this.context = context;
+        this.adHocRepository = adHocRepository;
+        this.adHocCommandFromApiJsonDeserializer = adHocCommandFromApiJsonDeserializer;
+       
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult createAdHocQuery(final JsonCommand command) {
+
+        try {
+            this.context.authenticatedUser();
+
+            this.adHocCommandFromApiJsonDeserializer.validateForCreate(command.json());
+
+            final AdHoc entity = AdHoc.fromJson(command);
+            this.adHocRepository.save(entity);
+
+            return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(entity.getId()).build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleDataIntegrityIssues(command, dve);
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .build();
+        }
+    }
+
+    /*
+     * Guaranteed to throw an exception no matter what the data integrity issue
+     * is.
+     */
+    private void handleDataIntegrityIssues(final JsonCommand command, final DataIntegrityViolationException dve) {
+
+        final Throwable realCause = dve.getMostSpecificCause();
+        if (realCause.getMessage().contains("unq_name")) {
+
+            final String name = command.stringValueOfParameterNamed("name");
+            throw new PlatformDataIntegrityException("error.msg.adhocquery.duplicate.name", "AdHocQuery with name `" + name + "` already exists",
+                    "name", name);
+        }
+
+        logAsErrorUnexpectedDataIntegrityException(dve);
+        throw new PlatformDataIntegrityException("error.msg.adhocquery.unknown.data.integrity.issue",
+                "Unknown data integrity issue with resource.");
+    }
+
+    private void logAsErrorUnexpectedDataIntegrityException(final DataIntegrityViolationException dve) {
+        logger.error(dve.getMessage(), dve);
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult updateAdHocQuery(final Long adHocId, final JsonCommand command) {
+        try {
+            this.context.authenticatedUser();
+
+            this.adHocCommandFromApiJsonDeserializer.validateForUpdate(command.json());
+
+            final AdHoc adHoc = this.adHocRepository.findOne(adHocId);
+            if (adHoc == null) { throw new AdHocNotFoundException(adHocId); }
+
+            final Map<String, Object> changes = adHoc.update(command);
+            if (!changes.isEmpty()) {
+                this.adHocRepository.saveAndFlush(adHoc);
+            }
+
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .withEntityId(adHocId) //
+                    .with(changes) //
+                    .build();
+        } catch (final DataIntegrityViolationException dve) {
+            handleDataIntegrityIssues(command, dve);
+            return new CommandProcessingResultBuilder() //
+                    .withCommandId(command.commandId()) //
+                    .build();
+        }
+    }
+    /**
+     * Method for Delete adhoc
+     */
+    @Transactional
+    @Override
+    public CommandProcessingResult deleteAdHocQuery(Long adHocId) {
+
+        try {
+            /**
+             * Checking the adhocQuery present in DB or not using adHocId
+             */
+            final AdHoc adHoc = this.adHocRepository.findOne(adHocId);
+            if (adHoc == null) { throw new AdHocNotFoundException(adHocId); }
+            
+            this.adHocRepository.delete(adHoc);
+            return new CommandProcessingResultBuilder().withEntityId(adHocId).build();
+        } catch (final DataIntegrityViolationException e) {
+            throw new PlatformDataIntegrityException("error.msg.unknown.data.integrity.issue",
+                    "Unknown data integrity issue with resource: " + e.getMostSpecificCause());
+        }
+    }
+
+    /**
+     * Method for disabling the adhocquery
+     */
+    @Transactional
+    @Override
+    public CommandProcessingResult disableAdHocQuery(Long adHocId) {
+        try {
+            /**
+             * Checking the adhocquery present in DB or not using adHocId
+             */
+            final AdHoc adHoc = this.adHocRepository.findOne(adHocId);
+            if (adHoc == null) { throw new AdHocNotFoundException(adHocId); }
+            adHoc.disableActive();
+            this.adHocRepository.save(adHoc);
+            return new CommandProcessingResultBuilder().withEntityId(adHocId).build();
+
+        } catch (final DataIntegrityViolationException e) {
+            throw new PlatformDataIntegrityException("error.msg.unknown.data.integrity.issue",
+                    "Unknown data integrity issue with resource: " + e.getMostSpecificCause());
+        }
+    }
+
+    /**
+     * Method for Enabling the Active
+     */
+    @Transactional
+    @Override
+    public CommandProcessingResult enableAdHocQuery(Long adHocId) {
+        try {
+            /**
+             * Checking the adHoc present in DB or not using id
+             */
+            final AdHoc adHoc = this.adHocRepository.findOne(adHocId);
+            if (adHoc == null) { throw new AdHocNotFoundException(adHocId); }
+            adHoc.enableActive();
+            this.adHocRepository.save(adHoc);
+            return new CommandProcessingResultBuilder().withEntityId(adHocId).build();
+
+        } catch (final DataIntegrityViolationException e) {
+            throw new PlatformDataIntegrityException("error.msg.unknown.data.integrity.issue",
+                    "Unknown data integrity issue with resource: " + e.getMostSpecificCause());
+        }
+    }
+}
\ No newline at end of file
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 a940150..0ad3612 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
@@ -3002,4 +3002,43 @@
         this.href = "/savingsaccounts/" + accountId + "?command=unblock";
         return this;
     }
+	public CommandWrapperBuilder disableAdHoc(Long adHocId) {
+        this.actionName = "DISABLE";
+        this.entityName = "ADHOC";
+        this.entityId = adHocId;
+        this.href = "/adhoc/" + adHocId + "/disbale";
+        this.json = "{}";
+        return this;
+    }
+
+    public CommandWrapperBuilder enableAdHoc(Long adHocId) {
+        this.actionName = "ENABLE";
+        this.entityName = "ADHOC";
+        this.entityId = adHocId;
+        this.href = "/adhoc/" + adHocId + "/enable";
+        this.json = "{}";
+        return this;
+    }
+    public CommandWrapperBuilder createAdHoc() {
+        this.actionName = "CREATE";
+        this.entityName = "ADHOC";
+        this.href = "/adhocquery/template";
+        return this;
+    }
+    public CommandWrapperBuilder updateAdHoc(final Long adHocId) {
+        this.actionName = "UPDATE";
+        this.entityName = "ADHOC";
+        this.entityId = adHocId;
+        this.href = "/adhocquery/" + adHocId;
+        return this;
+    }
+
+    public CommandWrapperBuilder deleteAdHoc(Long adHocId) {
+        this.actionName = "DELETE";
+        this.entityName = "ADHOC";
+        this.entityId = adHocId;
+        this.href = "/adhocquery/" + adHocId;
+        this.json = "{}";
+        return this;
+    }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/api/AccountNumberFormatsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/api/AccountNumberFormatsApiResource.java
index a420e2e..5d9601f 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/api/AccountNumberFormatsApiResource.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/api/AccountNumberFormatsApiResource.java
@@ -18,7 +18,10 @@
  */
 package org.apache.fineract.infrastructure.accountnumberformat.api;
 
+import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 import javax.ws.rs.Consumes;
 import javax.ws.rs.DELETE;
@@ -58,6 +61,10 @@
     private final ToApiJsonSerializer<AccountNumberFormatData> toApiJsonSerializer;
     private final ApiRequestParameterHelper apiRequestParameterHelper;
     private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
+	private static final Set<String> ACCOUNT_NUMBER_FORMAT_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(
+			AccountNumberFormatConstants.idParamName, AccountNumberFormatConstants.accountTypeParamName,
+			AccountNumberFormatConstants.prefixTypeParamName, AccountNumberFormatConstants.accountTypeOptionsParamName,
+			AccountNumberFormatConstants.prefixTypeOptionsParamName));
 
     @Autowired
     public AccountNumberFormatsApiResource(final PlatformSecurityContext context,
@@ -84,8 +91,8 @@
         AccountNumberFormatData accountNumberFormatData = this.accountNumberFormatReadPlatformService.retrieveTemplate(accountType);
 
         final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
-        return this.toApiJsonSerializer.serialize(settings, accountNumberFormatData,
-                AccountNumberFormatConstants.ACCOUNT_NUMBER_FORMAT_RESPONSE_DATA_PARAMETERS);
+		return this.toApiJsonSerializer.serialize(settings, accountNumberFormatData,
+				ACCOUNT_NUMBER_FORMAT_RESPONSE_DATA_PARAMETERS);
     }
 
     @GET
@@ -99,8 +106,8 @@
                 .getAllAccountNumberFormats();
 
         final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
-        return this.toApiJsonSerializer.serialize(settings, accountNumberFormatData,
-                AccountNumberFormatConstants.ACCOUNT_NUMBER_FORMAT_RESPONSE_DATA_PARAMETERS);
+		return this.toApiJsonSerializer.serialize(settings, accountNumberFormatData,
+				ACCOUNT_NUMBER_FORMAT_RESPONSE_DATA_PARAMETERS);
     }
 
     @GET
@@ -121,8 +128,8 @@
             accountNumberFormatData.templateOnTop(templateData.getAccountTypeOptions(), templateData.getPrefixTypeOptions());
         }
 
-        return this.toApiJsonSerializer.serialize(settings, accountNumberFormatData,
-                AccountNumberFormatConstants.ACCOUNT_NUMBER_FORMAT_RESPONSE_DATA_PARAMETERS);
+		return this.toApiJsonSerializer.serialize(settings, accountNumberFormatData,
+				ACCOUNT_NUMBER_FORMAT_RESPONSE_DATA_PARAMETERS);
     }
 
     @POST
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/data/AccountNumberFormatDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/data/AccountNumberFormatDataValidator.java
index 90cac29..a9b90f7 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/data/AccountNumberFormatDataValidator.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/data/AccountNumberFormatDataValidator.java
@@ -20,6 +20,7 @@
 
 import java.lang.reflect.Type;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -45,6 +46,11 @@
 public class AccountNumberFormatDataValidator {
 
     private final FromJsonHelper fromApiJsonHelper;
+    private static final Set<String> ACCOUNT_NUMBER_FORMAT_CREATE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(
+            AccountNumberFormatConstants.accountTypeParamName, AccountNumberFormatConstants.prefixTypeParamName));
+
+	private static final Set<String> ACCOUNT_NUMBER_FORMAT_UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<>(
+			Arrays.asList(AccountNumberFormatConstants.prefixTypeParamName));
 
     @Autowired
     public AccountNumberFormatDataValidator(final FromJsonHelper fromApiJsonHelper) {
@@ -55,8 +61,8 @@
 
         if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
         final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
-        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
-                AccountNumberFormatConstants.ACCOUNT_NUMBER_FORMAT_CREATE_REQUEST_DATA_PARAMETERS);
+		this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
+				ACCOUNT_NUMBER_FORMAT_CREATE_REQUEST_DATA_PARAMETERS);
         final JsonElement element = this.fromApiJsonHelper.parse(json);
         final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
 
@@ -137,8 +143,8 @@
         if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
 
         final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
-        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
-                AccountNumberFormatConstants.ACCOUNT_NUMBER_FORMAT_UPDATE_REQUEST_DATA_PARAMETERS);
+		this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
+				ACCOUNT_NUMBER_FORMAT_UPDATE_REQUEST_DATA_PARAMETERS);
         final JsonElement element = this.fromApiJsonHelper.parse(json);
 
         final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/service/AccountNumberFormatConstants.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/service/AccountNumberFormatConstants.java
index 4836d8b..722f6be 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/service/AccountNumberFormatConstants.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/service/AccountNumberFormatConstants.java
@@ -49,18 +49,11 @@
     public static final String accountTypeOptionsParamName = "accountTypeOptions";
     public static final String prefixTypeOptionsParamName = "prefixTypeOptions";
 
-    public static final Set<String> ACCOUNT_NUMBER_FORMAT_CREATE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(
-            accountTypeParamName, prefixTypeParamName));
-
-    public static final Set<String> ACCOUNT_NUMBER_FORMAT_UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(prefixTypeParamName));
-
     /**
      * These parameters will match the class level parameters of
      * {@link AccountNumberFormatData}. Where possible, we try to get response
      * parameters to match those of request parameters.
      */
-    public static final Set<String> ACCOUNT_NUMBER_FORMAT_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(idParamName,
-            accountTypeParamName, prefixTypeParamName, accountTypeOptionsParamName, prefixTypeOptionsParamName));
 
     // Error messages codes
     public static final String EXCEPTION_DUPLICATE_ACCOUNT_TYPE = "error.msg.account.number.format.duplicate.account.type";
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/CacheApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/CacheApiConstants.java
index ccbc7fd..7351980 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/CacheApiConstants.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/CacheApiConstants.java
@@ -26,6 +26,5 @@
 
     public static final String RESOURCE_NAME = "CACHE";
     public static final String cacheTypeParameter = "cacheType";
-    public static final Set<String> REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(cacheTypeParameter));
 
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/command/UpdateCacheCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/command/UpdateCacheCommandHandler.java
index 7d7e9fc..9cea2c2 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/command/UpdateCacheCommandHandler.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/command/UpdateCacheCommandHandler.java
@@ -20,8 +20,11 @@
 
 import java.lang.reflect.Type;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import org.apache.commons.lang.StringUtils;
 import org.apache.fineract.commands.annotation.CommandType;
@@ -47,6 +50,8 @@
 public class UpdateCacheCommandHandler implements NewCommandSourceHandler {
 
     private final CacheWritePlatformService cacheService;
+    private static final Set<String> REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(CacheApiConstants
+            .cacheTypeParameter));
 
     @Autowired
     public UpdateCacheCommandHandler(final CacheWritePlatformService cacheService) {
@@ -62,7 +67,7 @@
         if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
 
         final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
-        command.checkForUnsupportedParameters(typeOfMap, json, CacheApiConstants.REQUEST_DATA_PARAMETERS);
+        command.checkForUnsupportedParameters(typeOfMap, json, REQUEST_DATA_PARAMETERS);
 
         final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
         final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/exception/SmsRuntimeException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/exception/SmsRuntimeException.java
new file mode 100644
index 0000000..7ae714b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/exception/SmsRuntimeException.java
@@ -0,0 +1,28 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.campaigns.sms.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class SmsRuntimeException extends AbstractPlatformDomainRuleException {
+
+    public SmsRuntimeException(String errorCode, String errorMessage) {
+        super(errorCode, errorMessage);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDomainServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDomainServiceImpl.java
index 42d7b8b..3b0e0ae 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDomainServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignDomainServiceImpl.java
@@ -34,6 +34,7 @@
 import org.apache.fineract.infrastructure.campaigns.sms.constants.SmsCampaignTriggerType;
 import org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaign;
 import org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaignRepository;
+import org.apache.fineract.infrastructure.campaigns.sms.exception.SmsRuntimeException;
 import org.apache.fineract.infrastructure.sms.domain.SmsMessage;
 import org.apache.fineract.infrastructure.sms.domain.SmsMessageRepository;
 import org.apache.fineract.infrastructure.sms.scheduler.SmsMessageScheduledJobService;
@@ -204,10 +205,12 @@
 										Office campaignOffice = this.officeRepository.findOne(Long.valueOf(value));
 										if (campaignOffice
 												.doesNotHaveAnOfficeInHierarchyWithId(client.getOffice().getId())) {
-											throw new RuntimeException();
+											throw new SmsRuntimeException("error.msg.no.office",
+													"Office not found for the id");
 										}
 									} else {
-										throw new RuntimeException();
+										throw new SmsRuntimeException("error.msg.no.id.attribute",
+												"Office Id attribute is notfound");
 									}
 								}
 							}
@@ -258,10 +261,12 @@
 							if (key.equals("officeId")) {
 								Office campaignOffice = this.officeRepository.findOne(Long.valueOf(value));
 								if (campaignOffice.doesNotHaveAnOfficeInHierarchyWithId(client.getOffice().getId())) {
-									throw new RuntimeException();
+									throw new SmsRuntimeException("error.msg.no.office",
+											"Office not found for the id");
 								}
 							} else {
-								throw new RuntimeException();
+								throw new SmsRuntimeException("error.msg.no.id.attribute",
+										"Office Id attribute is notfound");
 							}
 						}
 					}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/JsonCommand.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/JsonCommand.java
index 9b8356d..76f30c6 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/JsonCommand.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/JsonCommand.java
@@ -122,6 +122,29 @@
         this.organisationCreditBureauId=organisationCreditBureauId;
     }
     
+    public static JsonCommand fromJsonElement(final Long resourceId, final JsonElement parsedCommand) {
+        return new JsonCommand(resourceId, parsedCommand);
+    }
+    
+    public JsonCommand(final Long resourceId, final JsonElement parsedCommand) {
+        this.parsedCommand = parsedCommand;
+        this.resourceId = resourceId;
+        this.commandId = null;
+        this.jsonCommand = null;        
+        this.fromApiJsonHelper = null;
+        this.entityName = null;
+        this.subresourceId = null;
+        this.groupId = null;
+        this.clientId = null;
+        this.loanId = null;
+        this.savingsId = null;
+        this.transactionId = null;
+        this.url = null;
+        this.productId = null;
+        this.creditBureauId=null;
+        this.organisationCreditBureauId=null;
+    }
+    
     public Long getOrganisationCreditBureauId() {
         return this.organisationCreditBureauId;
     }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java
index b0b75b1..f3a160f 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java
@@ -45,8 +45,8 @@
     EXECUTE_REPORT_MAILING_JOBS("Execute Report Mailing Jobs"),
     UPDATE_SMS_OUTBOUND_WITH_CAMPAIGN_MESSAGE("Update SMS Outbound with Campaign Message"),
     SEND_MESSAGES_TO_SMS_GATEWAY("Send Messages to SMS Gateway"), 
-    GET_DELIVERY_REPORTS_FROM_SMS_GATEWAY("Get Delivery Reports from SMS Gateway");
-
+    GET_DELIVERY_REPORTS_FROM_SMS_GATEWAY("Get Delivery Reports from SMS Gateway"),
+	GENERATE_ADHOCCLIENT_SCEHDULE("Generate AdhocClient Schedule");
     private final String name;
 
     private JobName(final String name) {
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/utils/SQLInjectionValidator.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/utils/SQLInjectionValidator.java
index 03785e0..60c2070 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/utils/SQLInjectionValidator.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/utils/SQLInjectionValidator.java
@@ -30,7 +30,7 @@
 
 	private final static String[] COMMENTS = { "--", "({", "/*", "#" };
 
-	private final static String SQL_PATTERN = "[a-zA-Z_=,'!><.?\"`% ()0-9]*";
+	private final static String SQL_PATTERN = "[a-zA-Z_=,\\-'!><.?\"`% ()0-9]*";
 
 	public final static void validateSQLInput(final String sqlSearch) {
 		String lowerCaseSQL = sqlSearch.toLowerCase();
@@ -114,4 +114,81 @@
 			throw new SQLInjectionException();
 		}
 	}
+	public final static void validateAdhocQuery(final String sqlSearch) {
+		String lowerCaseSQL = sqlSearch.toLowerCase();
+		for (String ddl : DDL_COMMANDS) {
+			if (lowerCaseSQL.contains(ddl)) {
+				throw new SQLInjectionException();
+			}
+		}
+
+		
+		for (String comments : COMMENTS) {
+			if (lowerCaseSQL.contains(comments)) {
+				throw new SQLInjectionException();
+			}
+		}
+
+		//Removing the space before and after '=' operator 
+		//String s = "          \"              OR 1    =    1"; For the cases like this
+		boolean injectionFound = false;
+		String inputSqlString = lowerCaseSQL;
+		while (inputSqlString.indexOf(" =") > 0) { //Don't remove space before = operator
+			inputSqlString = inputSqlString.replaceAll(" =", "=");
+		}
+
+		while (inputSqlString.indexOf("= ") > 0) { //Don't remove space after = operator
+			inputSqlString = inputSqlString.replaceAll("= ", "=");
+		}
+
+		StringTokenizer tokenizer = new StringTokenizer(inputSqlString, " ");
+		while (tokenizer.hasMoreTokens()) {
+			String token = tokenizer.nextToken().trim();
+			if (token.equals("'")) {
+				if (tokenizer.hasMoreElements()) {
+					String nextToken = tokenizer.nextToken().trim();
+					if (!nextToken.equals("'")) {
+						injectionFound = true;
+						break;
+					}
+				} else {
+					injectionFound = true;
+					break ;
+				}
+			}
+			if (token.equals("\"")) {
+				if (tokenizer.hasMoreElements()) {
+					String nextToken = tokenizer.nextToken().trim();
+					if (!nextToken.equals("\"")) {
+						injectionFound = true;
+						break;
+					}
+				} else {
+					injectionFound = true;
+					break ;
+				}
+			} else if (token.indexOf('=') > 0) {
+				StringTokenizer operatorToken = new StringTokenizer(token, "=");
+				String operand = operatorToken.nextToken().trim();
+				if (!operatorToken.hasMoreTokens()) {
+					injectionFound = true;
+					break;
+				}
+				String value = operatorToken.nextToken().trim();
+				if (operand.equals(value)) {
+					injectionFound = true;
+					break;
+				}
+			}
+		}
+		if (injectionFound) {
+			throw new SQLInjectionException();
+		}
+		
+		Pattern pattern = Pattern.compile(SQL_PATTERN);
+		Matcher matcher = pattern.matcher(sqlSearch);
+		if (!matcher.matches()) {
+			throw new SQLInjectionException();
+		}
+	}
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/api/HolidayApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/api/HolidayApiConstants.java
index 27fa538..dc019a5 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/api/HolidayApiConstants.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/api/HolidayApiConstants.java
@@ -41,6 +41,7 @@
     public static final String repaymentsRescheduledToParamName = "repaymentsRescheduledTo";
     public static final String processed = "processed";
     public static final String status = "status";
+    public static final String reschedulingType = "reschedulingType";
 
 	protected static final Set<String> HOLIDAY_RESPONSE_DATA_PARAMETERS = new HashSet<>(
 			Arrays.asList(idParamName, nameParamName, fromDateParamName, descriptionParamName, toDateParamName,
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/api/HolidaysApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/api/HolidaysApiResource.java
index 061f225..e8b22d3 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/api/HolidaysApiResource.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/api/HolidaysApiResource.java
@@ -180,4 +180,13 @@
         final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
         return this.toApiJsonSerializer.serialize(settings, holidays, HOLIDAY_RESPONSE_DATA_PARAMETERS);
     }
+    
+    @GET
+    @Path("/template")
+    @Consumes({MediaType.APPLICATION_JSON})
+    @Produces({MediaType.APPLICATION_JSON})
+    public String retrieveRepaymentScheduleUpdationTyeOptions(@Context final UriInfo uriInfo){
+        this.context.authenticatedUser().validateHasReadPermission(HOLIDAY_RESOURCE_NAME);
+        return this.toApiJsonSerializer.serialize(this.holidayReadPlatformService.retrieveRepaymentScheduleUpdationTyeOptions());
+    }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/data/HolidayData.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/data/HolidayData.java
index 70f9839..12c2d0a 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/data/HolidayData.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/data/HolidayData.java
@@ -39,9 +39,10 @@
     private final Long officeId;
     @SuppressWarnings("unused")
     private final EnumOptionData status;
+    private final Integer reschedulingType;
 
     public HolidayData(final Long id, final String name, final String description, final LocalDate fromDate, final LocalDate toDate,
-            final LocalDate repaymentsRescheduledTo, final EnumOptionData status) {
+            final LocalDate repaymentsRescheduledTo, final EnumOptionData status, final Integer reschedulingType) {
         this.id = id;
         this.name = name;
         this.description = description;
@@ -50,5 +51,6 @@
         this.repaymentsRescheduledTo = repaymentsRescheduledTo;
         this.officeId = null;
         this.status = status;
+        this.reschedulingType = reschedulingType;
     }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/data/HolidayDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/data/HolidayDataValidator.java
index e99eafb..755292c 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/data/HolidayDataValidator.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/data/HolidayDataValidator.java
@@ -23,6 +23,7 @@
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 
@@ -33,6 +34,7 @@
 import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
 import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
 import org.apache.fineract.organisation.holiday.api.HolidayApiConstants;
+import org.apache.fineract.organisation.holiday.domain.RescheduleType;
 import org.joda.time.LocalDate;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
@@ -50,7 +52,8 @@
 			Arrays.asList(HolidayApiConstants.localeParamName, HolidayApiConstants.dateFormatParamName,
 					HolidayApiConstants.nameParamName, HolidayApiConstants.fromDateParamName,
 					HolidayApiConstants.toDateParamName, HolidayApiConstants.descriptionParamName,
-					HolidayApiConstants.officesParamName, HolidayApiConstants.repaymentsRescheduledToParamName));
+					HolidayApiConstants.officesParamName, HolidayApiConstants.repaymentsRescheduledToParamName,
+					HolidayApiConstants.reschedulingType));
 
     @Autowired
     public HolidayDataValidator(final FromJsonHelper fromApiJsonHelper) {
@@ -79,11 +82,18 @@
 
         final LocalDate toDate = this.fromApiJsonHelper.extractLocalDateNamed(HolidayApiConstants.toDateParamName, element);
         baseDataValidator.reset().parameter(HolidayApiConstants.toDateParamName).value(toDate).notNull();
-
-        final LocalDate repaymentsRescheduledTo = this.fromApiJsonHelper.extractLocalDateNamed(
-                HolidayApiConstants.repaymentsRescheduledToParamName, element);
-        baseDataValidator.reset().parameter(HolidayApiConstants.repaymentsRescheduledToParamName).value(repaymentsRescheduledTo).notNull();
-
+        
+        Integer reschedulingType = null;
+        if(this.fromApiJsonHelper.parameterExists(HolidayApiConstants.reschedulingType, element)){
+            reschedulingType = this.fromApiJsonHelper.extractIntegerNamed(HolidayApiConstants.reschedulingType, element, Locale.ENGLISH);
+        }
+       
+        LocalDate repaymentsRescheduledTo = null;
+        if(reschedulingType == null || reschedulingType.equals(RescheduleType.RESCHEDULETOSPECIFICDATE.getValue())){
+            repaymentsRescheduledTo = this.fromApiJsonHelper.extractLocalDateNamed(
+                    HolidayApiConstants.repaymentsRescheduledToParamName, element);
+            baseDataValidator.reset().parameter(HolidayApiConstants.repaymentsRescheduledToParamName).value(repaymentsRescheduledTo).notNull();
+        }
         Set<Long> offices = null;
         final JsonObject topLevelJsonElement = element.getAsJsonObject();
 
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/domain/Holiday.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/domain/Holiday.java
index be79b25..c25a3bf 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/domain/Holiday.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/domain/Holiday.java
@@ -73,9 +73,12 @@
     @Temporal(TemporalType.DATE)
     private Date toDate;
 
-    @Column(name = "repayments_rescheduled_to", nullable = false)
+    @Column(name = "repayments_rescheduled_to", nullable = true)
     @Temporal(TemporalType.DATE)
     private Date repaymentsRescheduledTo;
+    
+    @Column(name = "rescheduling_type", nullable = false)
+    private int reschedulingType;
 
     @Column(name = "status_enum", nullable = false)
     private Integer status;
@@ -94,13 +97,21 @@
         final String name = command.stringValueOfParameterNamed(HolidayApiConstants.nameParamName);
         final LocalDate fromDate = command.localDateValueOfParameterNamed(HolidayApiConstants.fromDateParamName);
         final LocalDate toDate = command.localDateValueOfParameterNamed(HolidayApiConstants.toDateParamName);
-        final LocalDate repaymentsRescheduledTo = command
-                .localDateValueOfParameterNamed(HolidayApiConstants.repaymentsRescheduledToParamName);
+        Integer reschedulingType = null;
+        if(command.parameterExists(HolidayApiConstants.reschedulingType)){
+            reschedulingType = command.integerValueOfParameterNamed(HolidayApiConstants.reschedulingType);
+        }
+        LocalDate repaymentsRescheduledTo = null;
+        if(reschedulingType == null || reschedulingType.equals(RescheduleType.RESCHEDULETOSPECIFICDATE.getValue())){
+            repaymentsRescheduledTo = command
+                    .localDateValueOfParameterNamed(HolidayApiConstants.repaymentsRescheduledToParamName);
+        }
         final Integer status = HolidayStatusType.PENDING_FOR_ACTIVATION.getValue();
         final boolean processed = false;// default it to false. Only batch job
                                         // should update this field.
         final String description = command.stringValueOfParameterNamed(HolidayApiConstants.descriptionParamName);
-        return new Holiday(name, fromDate, toDate, repaymentsRescheduledTo, status, processed, description, offices);
+        
+        return new Holiday(name, fromDate, toDate, repaymentsRescheduledTo, status, processed, description, offices, reschedulingType);
     }
 
     public Map<String, Object> update(final JsonCommand command) {
@@ -125,6 +136,16 @@
             actualChanges.put(descriptionParamName, newValue);
             this.description = StringUtils.defaultIfEmpty(newValue, null);
         }
+        
+        
+        if (command.isChangeInIntegerParameterNamed(HolidayApiConstants.reschedulingType,this.reschedulingType)) {
+            final Integer newValue =command.integerValueOfParameterNamed(HolidayApiConstants.reschedulingType);
+            actualChanges.put(HolidayApiConstants.reschedulingType, newValue);
+            this.reschedulingType = RescheduleType.fromInt(newValue).getValue();
+            if(newValue.equals(RescheduleType.RESCHEDULETONEXTREPAYMENTDATE.getValue())){
+                this.repaymentsRescheduledTo = null;
+            }
+        } 
 
         if (currentStatus.isPendingActivation()) {
             if (command.isChangeInLocalDateParameterNamed(fromDateParamName, getFromDateLocalDate())) {
@@ -205,7 +226,7 @@
     }
 
     private Holiday(final String name, final LocalDate fromDate, final LocalDate toDate, final LocalDate repaymentsRescheduledTo,
-            final Integer status, final boolean processed, final String description, final Set<Office> offices) {
+            final Integer status, final boolean processed, final String description, final Set<Office> offices, final int reschedulingType) {
         if (StringUtils.isNotBlank(name)) {
             this.name = name.trim();
         }
@@ -234,6 +255,7 @@
         if (offices != null) {
             this.offices = offices;
         }
+        this.reschedulingType = reschedulingType;
     }
 
     protected Holiday() {}
@@ -298,4 +320,8 @@
         }
         this.status = HolidayStatusType.DELETED.getValue();
     }
+    
+    public RescheduleType getReScheduleType() {
+        return RescheduleType.fromInt(this.reschedulingType);
+    }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/domain/RescheduleType.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/domain/RescheduleType.java
new file mode 100644
index 0000000..a87b658
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/domain/RescheduleType.java
@@ -0,0 +1,64 @@
+/**
+ * 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.organisation.holiday.domain;
+
+public enum RescheduleType {
+
+    INVALID(0, "rescheduletype.invalid"), RESCHEDULETOSPECIFICDATE(2, "rescheduletype.rescheduletospecificdate"), //
+    RESCHEDULETONEXTREPAYMENTDATE(1, "rescheduletype.rescheduletonextrepaymentdate");
+
+    private final Integer value;
+    private final String code;
+
+    private RescheduleType(Integer value, String code) {
+            this.value = value;
+            this.code = code;
+    }
+
+    public static RescheduleType fromInt(int rescheduleTypeValue) {
+            RescheduleType enumerration = RescheduleType.INVALID;
+            switch (rescheduleTypeValue) {
+            case 1:
+                    enumerration = RescheduleType.RESCHEDULETONEXTREPAYMENTDATE;
+                    break;
+            case 2:
+                    enumerration = RescheduleType.RESCHEDULETOSPECIFICDATE;
+                    break;
+            }
+            return enumerration;
+    }
+
+    public boolean isRescheduleToSpecificDate(){
+            return this.value.equals(RescheduleType.RESCHEDULETOSPECIFICDATE.getValue());
+    }
+    
+    public boolean isResheduleToNextRepaymentDate(){
+            return this.value.equals(RescheduleType.RESCHEDULETONEXTREPAYMENTDATE.getValue());
+    }
+
+    
+    public Integer getValue() {
+            return this.value;
+    }
+
+    public String getCode() {
+            return this.code;
+    }
+    
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayEnumerations.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayEnumerations.java
index 43e22d0..176a3c8 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayEnumerations.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayEnumerations.java
@@ -20,6 +20,7 @@
 
 import org.apache.fineract.infrastructure.core.data.EnumOptionData;
 import org.apache.fineract.organisation.holiday.domain.HolidayStatusType;
+import org.apache.fineract.organisation.holiday.domain.RescheduleType;
 
 public class HolidayEnumerations {
 
@@ -49,4 +50,29 @@
         }
         return optionData;
     }
+    
+    public static EnumOptionData rescheduleType(final int id) {
+        return rescheduleType(RescheduleType.fromInt(id));
+    }
+
+    
+    public static EnumOptionData rescheduleType(final RescheduleType type) {
+        EnumOptionData optionData = null;
+        switch (type) {
+            case RESCHEDULETONEXTREPAYMENTDATE:
+                optionData = new EnumOptionData(RescheduleType.RESCHEDULETONEXTREPAYMENTDATE.getValue().longValue(),
+                        RescheduleType.RESCHEDULETONEXTREPAYMENTDATE.getCode(), "Reschedule to next repayment date");
+            break;
+            case RESCHEDULETOSPECIFICDATE:
+                optionData = new EnumOptionData(RescheduleType.RESCHEDULETOSPECIFICDATE.getValue().longValue(),
+                        RescheduleType.RESCHEDULETOSPECIFICDATE.getCode(), "Reschedule to specified date");
+            break;
+           
+            default:
+                optionData = new EnumOptionData(RescheduleType.INVALID.getValue().longValue(),
+                        RescheduleType.INVALID.getCode(), "Invalid");
+            break;
+        }
+        return optionData;
+    }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayReadPlatformService.java
index 309af96..9ca4b3f 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayReadPlatformService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayReadPlatformService.java
@@ -20,7 +20,9 @@
 
 import java.util.Collection;
 import java.util.Date;
+import java.util.List;
 
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
 import org.apache.fineract.organisation.holiday.data.HolidayData;
 
 public interface HolidayReadPlatformService {
@@ -28,4 +30,6 @@
     Collection<HolidayData> retrieveAllHolidaysBySearchParamerters(final Long officeId, Date fromDate, Date toDate);
 
     HolidayData retrieveHoliday(final Long holidayId);
+    
+    List<EnumOptionData> retrieveRepaymentScheduleUpdationTyeOptions();
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayReadPlatformServiceImpl.java
index 2601d21..96ccb72 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayReadPlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayReadPlatformServiceImpl.java
@@ -25,12 +25,14 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Date;
+import java.util.List;
 
 import org.apache.fineract.infrastructure.core.data.EnumOptionData;
 import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
 import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
 import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
 import org.apache.fineract.organisation.holiday.data.HolidayData;
+import org.apache.fineract.organisation.holiday.domain.RescheduleType;
 import org.apache.fineract.organisation.holiday.exception.HolidayNotFoundException;
 import org.joda.time.LocalDate;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -58,7 +60,7 @@
         public HolidayMapper() {
             final StringBuilder sqlBuilder = new StringBuilder(200);
             sqlBuilder.append("h.id as id, h.name as name, h.description as description, h.from_date as fromDate, h.to_date as toDate, ");
-            sqlBuilder.append("h.repayments_rescheduled_to as repaymentsScheduleTO, h.status_enum as statusEnum ");
+            sqlBuilder.append("h.repayments_rescheduled_to as repaymentsScheduleTO, h.rescheduling_type as reschedulingType, h.status_enum as statusEnum ");
             sqlBuilder.append("from m_holiday h ");
             this.schema = sqlBuilder.toString();
         }
@@ -76,9 +78,10 @@
             final LocalDate toDate = JdbcSupport.getLocalDate(rs, "toDate");
             final LocalDate repaymentsScheduleTO = JdbcSupport.getLocalDate(rs, "repaymentsScheduleTO");
             final Integer statusEnum = JdbcSupport.getInteger(rs, "statusEnum");
+            final Integer reschedulingType = JdbcSupport.getInteger(rs, "reschedulingType");
             final EnumOptionData status = HolidayEnumerations.holidayStatusType(statusEnum);
 
-            return new HolidayData(id, name, description, fromDate, toDate, repaymentsScheduleTO, status);
+            return new HolidayData(id, name, description, fromDate, toDate, repaymentsScheduleTO, status, reschedulingType);
         }
 
     }
@@ -133,5 +136,14 @@
             throw new HolidayNotFoundException(holidayId);
         }
     }
+    
+    @Override
+    public List<EnumOptionData> retrieveRepaymentScheduleUpdationTyeOptions(){
+        
+        final List<EnumOptionData> repSchUpdationTypeOptions = Arrays.asList(
+                HolidayEnumerations.rescheduleType(RescheduleType.RESCHEDULETOSPECIFICDATE),
+                HolidayEnumerations.rescheduleType(RescheduleType.RESCHEDULETONEXTREPAYMENTDATE));
+        return repSchUpdationTypeOptions;
+    }
 
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayUtil.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayUtil.java
index c026ac1..ba729f0 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayUtil.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayUtil.java
@@ -21,6 +21,7 @@
 import java.util.List;
 
 import org.apache.fineract.organisation.holiday.domain.Holiday;
+import org.apache.fineract.organisation.workingdays.data.AdjustedDateDetailsDTO;
 import org.joda.time.LocalDate;
 
 public class HolidayUtil {
@@ -56,4 +57,20 @@
 
         return false;
     }
+    
+    public static Holiday getApplicableHoliday(final LocalDate repaymentDate, final List<Holiday> holidays) {
+        Holiday referedHoliday = null;
+        for (final Holiday holiday : holidays) {
+            if (!repaymentDate.isBefore(holiday.getFromDateLocalDate()) && !repaymentDate.isAfter(holiday.getToDateLocalDate())) {
+                referedHoliday = holiday;
+            }
+        }
+        return referedHoliday;
+    }
+    public static void updateRepaymentRescheduleDateToWorkingDayIfItIsHoliday(final AdjustedDateDetailsDTO adjustedDateDetailsDTO,
+            final Holiday holiday) {
+        if (holiday.getReScheduleType().isRescheduleToSpecificDate()) {
+            adjustedDateDetailsDTO.setChangedScheduleDate(holiday.getRepaymentsRescheduledToLocalDate());
+        }
+    }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayWritePlatformServiceJpaRepositoryImpl.java
index 6bce676..6ff9818 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayWritePlatformServiceJpaRepositoryImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/holiday/service/HolidayWritePlatformServiceJpaRepositoryImpl.java
@@ -194,9 +194,18 @@
     private void validateInputDates(final JsonCommand command) {
         final LocalDate fromDate = command.localDateValueOfParameterNamed(HolidayApiConstants.fromDateParamName);
         final LocalDate toDate = command.localDateValueOfParameterNamed(HolidayApiConstants.toDateParamName);
-        final LocalDate repaymentsRescheduledTo = command
-                .localDateValueOfParameterNamed(HolidayApiConstants.repaymentsRescheduledToParamName);
-        this.validateInputDates(fromDate, toDate, repaymentsRescheduledTo);
+        Integer reshedulingType = null;
+        if(command.parameterExists(HolidayApiConstants.reschedulingType)){
+            reshedulingType = command.integerValueOfParameterNamed(HolidayApiConstants.reschedulingType);
+        }
+        LocalDate repaymentsRescheduledTo = null;
+        if(reshedulingType != null && reshedulingType.equals(2)){
+            repaymentsRescheduledTo = command
+                    .localDateValueOfParameterNamed(HolidayApiConstants.repaymentsRescheduledToParamName);
+        }
+        if(repaymentsRescheduledTo != null){
+            this.validateInputDates(fromDate, toDate, repaymentsRescheduledTo);
+        }
     }
 
     private void validateInputDates(final LocalDate fromDate, final LocalDate toDate, final LocalDate repaymentsRescheduledTo) {
@@ -208,37 +217,40 @@
             throw new HolidayDateException("to.date.cannot.be.before.from.date", defaultUserMessage, fromDate.toString(),
                     toDate.toString());
         }
+        if(repaymentsRescheduledTo != null){
+            if ((repaymentsRescheduledTo.isEqual(fromDate) || repaymentsRescheduledTo.isEqual(toDate)
+                    || (repaymentsRescheduledTo.isAfter(fromDate) && repaymentsRescheduledTo.isBefore(toDate)))) {
 
-        if (repaymentsRescheduledTo.isEqual(fromDate) || repaymentsRescheduledTo.isEqual(toDate)
-                || (repaymentsRescheduledTo.isAfter(fromDate) && repaymentsRescheduledTo.isBefore(toDate))) {
+                defaultUserMessage = "Repayments rescheduled date should be before from date or after to date.";
+                throw new HolidayDateException("repayments.rescheduled.date.should.be.before.from.date.or.after.to.date", defaultUserMessage,
+                        repaymentsRescheduledTo.toString());
+            }
 
-            defaultUserMessage = "Repayments rescheduled date should be before from date or after to date.";
-            throw new HolidayDateException("repayments.rescheduled.date.should.be.before.from.date.or.after.to.date", defaultUserMessage,
-                    repaymentsRescheduledTo.toString());
+            final WorkingDays workingDays = this.daysRepositoryWrapper.findOne();
+            final Boolean isRepaymentOnWorkingDay = WorkingDaysUtil.isWorkingDay(workingDays, repaymentsRescheduledTo);
+
+            if (!isRepaymentOnWorkingDay) {
+                defaultUserMessage = "Repayments rescheduled date should not fall on non working days";
+                throw new HolidayDateException("repayments.rescheduled.date.should.not.fall.on.non.working.day", defaultUserMessage,
+                        repaymentsRescheduledTo.toString());
+            }
+
+            // validate repaymentsRescheduledTo date
+            // 1. should be within a 7 days date range.
+            // 2. Alternative date should not be an exist holiday.//TBD
+            // 3. Holiday should not be on an repaymentsRescheduledTo date of
+            // another holiday.//TBD
+
+            // restricting repaymentsRescheduledTo date to be within 7 days range
+            // before or after from date and to date.
+            if (repaymentsRescheduledTo.isBefore(fromDate.minusDays(7)) || repaymentsRescheduledTo.isAfter(toDate.plusDays(7))) {
+                defaultUserMessage = "Repayments Rescheduled to date must be within 7 days before or after from and to dates";
+                throw new HolidayDateException("repayments.rescheduled.to.must.be.within.range", defaultUserMessage, fromDate.toString(),
+                        toDate.toString(), repaymentsRescheduledTo.toString());
+            }
         }
 
-        final WorkingDays workingDays = this.daysRepositoryWrapper.findOne();
-        final Boolean isRepaymentOnWorkingDay = WorkingDaysUtil.isWorkingDay(workingDays, repaymentsRescheduledTo);
 
-        if (!isRepaymentOnWorkingDay) {
-            defaultUserMessage = "Repayments rescheduled date should not fall on non working days";
-            throw new HolidayDateException("repayments.rescheduled.date.should.not.fall.on.non.working.day", defaultUserMessage,
-                    repaymentsRescheduledTo.toString());
-        }
-
-        // validate repaymentsRescheduledTo date
-        // 1. should be within a 7 days date range.
-        // 2. Alternative date should not be an exist holiday.//TBD
-        // 3. Holiday should not be on an repaymentsRescheduledTo date of
-        // another holiday.//TBD
-
-        // restricting repaymentsRescheduledTo date to be within 7 days range
-        // before or after from date and to date.
-        if (repaymentsRescheduledTo.isBefore(fromDate.minusDays(7)) || repaymentsRescheduledTo.isAfter(toDate.plusDays(7))) {
-            defaultUserMessage = "Repayments Rescheduled to date must be within 7 days before or after from and to dates";
-            throw new HolidayDateException("repayments.rescheduled.to.must.be.within.range", defaultUserMessage, fromDate.toString(),
-                    toDate.toString(), repaymentsRescheduledTo.toString());
-        }
     }
 
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/AdjustedDateDetailsDTO.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/AdjustedDateDetailsDTO.java
new file mode 100644
index 0000000..916c2fd
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/AdjustedDateDetailsDTO.java
@@ -0,0 +1,76 @@
+/**
+ * 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.organisation.workingdays.data;
+
+import org.joda.time.LocalDate;
+
+
+public class AdjustedDateDetailsDTO {
+
+    /**
+     * Variable tracks the current schedule date that has been changed
+     */
+    LocalDate changedScheduleDate;
+    /**
+     * Variable tracks If the meeting has been changed , i.e future schedule
+     * also changes along with the current repayments date.
+     */
+    LocalDate changedActualRepaymentDate;
+
+    /**
+     * Variable tracks the next repayment period due date
+     */
+    LocalDate nextRepaymentPeriodDueDate;
+
+    public AdjustedDateDetailsDTO(final LocalDate changedScheduleDate, final LocalDate changedActualRepaymentDate) {
+        this.changedScheduleDate = changedScheduleDate;
+        this.changedActualRepaymentDate = changedActualRepaymentDate;
+    }
+
+    public AdjustedDateDetailsDTO(final LocalDate changedScheduleDate, final LocalDate changedActualRepaymentDate,
+            final LocalDate nextRepaymentPeriodDueDate) {
+        this.changedScheduleDate = changedScheduleDate;
+        this.changedActualRepaymentDate = changedActualRepaymentDate;
+        this.nextRepaymentPeriodDueDate = nextRepaymentPeriodDueDate;
+    }
+
+    public LocalDate getChangedScheduleDate() {
+        return this.changedScheduleDate;
+    }
+
+    public LocalDate getChangedActualRepaymentDate() {
+        return this.changedActualRepaymentDate;
+    }
+
+    public void setChangedScheduleDate(final LocalDate changedScheduleDate) {
+        this.changedScheduleDate = changedScheduleDate;
+    }
+
+    public void setChangedActualRepaymentDate(final LocalDate changedActualRepaymentDate) {
+        this.changedActualRepaymentDate = changedActualRepaymentDate;
+    }
+
+    public LocalDate getNextRepaymentPeriodDueDate() {
+        return this.nextRepaymentPeriodDueDate;
+    }
+
+    public void setNextRepaymentPeriodDueDate(final LocalDate nextRepaymentPeriodDueDate) {
+        this.nextRepaymentPeriodDueDate = nextRepaymentPeriodDueDate;
+    }
+}
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/domain/RepaymentRescheduleType.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/domain/RepaymentRescheduleType.java
index 0194cd7..d658f92 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/domain/RepaymentRescheduleType.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/domain/RepaymentRescheduleType.java
@@ -26,7 +26,8 @@
     INVALID(0, "RepaymentRescheduleType.invalid"), SAME_DAY(1, "RepaymentRescheduleType.same.day"), MOVE_TO_NEXT_WORKING_DAY(2,
             "RepaymentRescheduleType.move.to.next.working.day"), MOVE_TO_NEXT_REPAYMENT_MEETING_DAY(3,
             "RepaymentRescheduleType.move.to.next.repayment.meeting.day"), MOVE_TO_PREVIOUS_WORKING_DAY(4,
-            "RepaymentRescheduleType.move.to.previous.working.day");
+            "RepaymentRescheduleType.move.to.previous.working.day"),
+            MOVE_TO_NEXT_MEETING_DAY(5, "RepaymentRescheduleType.move.to.next.meeting.day");
 
     private final Integer value;
     private final String code;
@@ -43,6 +44,10 @@
     public String getCode() {
         return this.code;
     }
+    
+    public boolean isMoveToNextRepaymentDay() {
+        return this.value.equals(RepaymentRescheduleType.MOVE_TO_NEXT_REPAYMENT_MEETING_DAY.getValue());
+    }
 
     private static final Map<Integer, RepaymentRescheduleType> intToEnumMap = new HashMap<>();
     private static int minValue;
diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysUtil.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysUtil.java
index ac0fc39..605a891 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysUtil.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysUtil.java
@@ -18,6 +18,7 @@
  */
 package org.apache.fineract.organisation.workingdays.service;
 
+import org.apache.fineract.organisation.workingdays.data.AdjustedDateDetailsDTO;
 import org.apache.fineract.organisation.workingdays.domain.RepaymentRescheduleType;
 import org.apache.fineract.organisation.workingdays.domain.WorkingDays;
 import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
@@ -56,4 +57,15 @@
     public static boolean isNonWorkingDay(final WorkingDays workingDays, final LocalDate date) {
         return !isWorkingDay(workingDays, date);
     }
+    
+    public static void updateWorkingDayIfRepaymentDateIsNonWorkingDay(final AdjustedDateDetailsDTO adjustedDateDetailsDTO, final WorkingDays workingDays) {
+        final LocalDate changedScheduleDate = getOffSetDateIfNonWorkingDay(adjustedDateDetailsDTO.getChangedScheduleDate(),
+                adjustedDateDetailsDTO.getNextRepaymentPeriodDueDate(), workingDays);
+        adjustedDateDetailsDTO.setChangedScheduleDate(changedScheduleDate);
+    }
+    
+    public static RepaymentRescheduleType getRepaymentRescheduleType(final WorkingDays workingDays, final LocalDate date) {
+        RepaymentRescheduleType rescheduleType = RepaymentRescheduleType.fromInt(workingDays.getRepaymentReschedulingType());
+        return rescheduleType;
+    }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarUtils.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarUtils.java
index 6360380..c7f10cb 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarUtils.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarUtils.java
@@ -700,4 +700,112 @@
         }
         return monthOnDay;
     }
+    
+    public static LocalDate getNextRepaymentMeetingDate(final String recurringRule, final LocalDate seedDate,
+            final LocalDate repaymentDate, final Integer loanRepaymentInterval, final String frequency, final WorkingDays workingDays,
+            boolean isSkipRepaymentOnFirstDayOfMonth, final Integer numberOfDays, boolean applyWorkingDays) {
+        boolean isCalledFirstTime = true;
+        return getNextRepaymentMeetingDate(recurringRule, seedDate, repaymentDate, loanRepaymentInterval, frequency,
+                workingDays, isSkipRepaymentOnFirstDayOfMonth, numberOfDays, isCalledFirstTime, applyWorkingDays);
+    }
+    
+    public static LocalDate getNextRepaymentMeetingDate(final String recurringRule, final LocalDate seedDate,
+            final LocalDate repaymentDate, final Integer loanRepaymentInterval, final String frequency, 
+            boolean isSkipRepaymentOnFirstDayOfMonth, final Integer numberOfDays) {
+        boolean isCalledFirstTime = true;
+        final WorkingDays workingDays = null;
+        boolean applyWorkingDays = false;
+        return getNextRepaymentMeetingDate(recurringRule, seedDate, repaymentDate, loanRepaymentInterval, frequency,
+                workingDays, isSkipRepaymentOnFirstDayOfMonth, numberOfDays, isCalledFirstTime, applyWorkingDays);
+    }
+    
+    public static LocalDate getNextRepaymentMeetingDate(final String recurringRule, final LocalDate seedDate,
+            final LocalDate repaymentDate, final Integer loanRepaymentInterval, final String frequency, final WorkingDays workingDays,
+            boolean isSkipRepaymentOnFirstDayOfMonth, final Integer numberOfDays, boolean isCalledFirstTime, boolean applyWorkingDays) {
+
+        final Recur recur = CalendarUtils.getICalRecur(recurringRule);
+        if (recur == null) { return null; }
+        LocalDate tmpDate = repaymentDate;
+        
+        final Integer repaymentInterval = getMeetingIntervalFromFrequency(loanRepaymentInterval, frequency, recur);
+        /*
+         * Recurring dates should follow loanRepaymentInterval.
+         * 
+         * e.g. The weekly meeting will have interval of 1, if the loan product
+         * with fortnightly frequency will have interval of 2, to generate right
+         * set of meeting dates reset interval same as loan repayment interval.
+         */
+        int meetingInterval = recur.getInterval();
+        if(meetingInterval < 1){
+                meetingInterval = 1;
+        }
+        int rep = repaymentInterval<meetingInterval ? 1: repaymentInterval / meetingInterval ;
+
+        /*
+         * Recurring dates should follow loanRepayment frequency. //e.g. daily
+         * meeting frequency should support all loan products with any type of
+         * frequency. to generate right set of meeting dates reset frequency
+         * same as loan repayment frequency.
+         */
+        if (recur.getFrequency().equals(Recur.DAILY)) {
+            recur.setFrequency(frequency);
+        }
+        
+        /**
+         * Below code modified as discussed with Pramod N 
+         */
+        LocalDate newRepaymentDate = tmpDate;
+        int newRepayment = rep;
+        while (newRepayment > 0) {
+            newRepaymentDate = getNextRecurringDate(recur, seedDate, newRepaymentDate);
+            newRepayment--;
+        }
+
+        LocalDate nextRepaymentDate = null;
+        if (applyWorkingDays) {
+            if (WorkingDaysUtil.isNonWorkingDay(workingDays, newRepaymentDate)
+                    && WorkingDaysUtil.getRepaymentRescheduleType(workingDays, newRepaymentDate).isMoveToNextRepaymentDay()) {
+                newRepaymentDate = getNextRepaymentMeetingDate(recurringRule, seedDate, newRepaymentDate.plusDays(1),
+                        loanRepaymentInterval, frequency, workingDays, isSkipRepaymentOnFirstDayOfMonth, numberOfDays, isCalledFirstTime,
+                        applyWorkingDays);
+            } else {
+                newRepaymentDate = WorkingDaysUtil.getOffSetDateIfNonWorkingDay(newRepaymentDate, nextRepaymentDate, workingDays);
+            }
+        }
+        
+        if(isCalledFirstTime && newRepaymentDate.equals(repaymentDate)){
+            isCalledFirstTime = false;
+            newRepaymentDate = getNextRepaymentMeetingDate(recurringRule, seedDate, repaymentDate.plusDays(1), loanRepaymentInterval,
+                    frequency, workingDays, isSkipRepaymentOnFirstDayOfMonth, numberOfDays, isCalledFirstTime, applyWorkingDays);
+        }
+        
+        if (isSkipRepaymentOnFirstDayOfMonth) {
+            final LocalDate newRepaymentDateTemp = adjustRecurringDate(newRepaymentDate, numberOfDays);
+            if (applyWorkingDays) {
+                if (WorkingDaysUtil.isNonWorkingDay(workingDays, newRepaymentDateTemp)
+                        && WorkingDaysUtil.getRepaymentRescheduleType(workingDays, newRepaymentDateTemp).isMoveToNextRepaymentDay()) {
+                    newRepaymentDate = getNextRepaymentMeetingDate(recurringRule, seedDate, newRepaymentDate.plusDays(1),
+                            loanRepaymentInterval, frequency, workingDays, isSkipRepaymentOnFirstDayOfMonth, numberOfDays,
+                            isCalledFirstTime, applyWorkingDays);
+                } else {
+                    newRepaymentDate = WorkingDaysUtil.getOffSetDateIfNonWorkingDay(newRepaymentDateTemp, nextRepaymentDate, workingDays);
+                }
+            }
+        }
+        return newRepaymentDate;
+    }
+    
+    public static Integer getMeetingIntervalFromFrequency(final Integer loanRepaymentInterval, final String frequency, final Recur recur) {
+        final Integer interval = 4;
+        Integer repaymentInterval = loanRepaymentInterval;
+        /*
+         * check loanRepaymentInterval equal to 1, if repayments frequency is
+         * monthly and meeting frequency is weekly, then generate repayments
+         * schedule as every 4 weeks
+         */
+        if (frequency.equals(Recur.MONTHLY) && recur.getFrequency().equals(Recur.WEEKLY)) {
+            repaymentInterval = loanRepaymentInterval*interval;
+        }
+        return repaymentInterval;
+    }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientRepository.java
index e718265..eb2714c 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientRepository.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientRepository.java
@@ -20,7 +20,13 @@
 
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
 
 abstract interface ClientRepository extends JpaRepository<Client, Long>, JpaSpecificationExecutor<Client> {
-    // no added behaviour
+    
+    public static final String FIND_CLIENT_BY_ACCOUNT_NUMBER = "select client from Client client where client.accountNumber = :accountNumber";
+
+    @Query(FIND_CLIENT_BY_ACCOUNT_NUMBER)
+    Client getClientByAccountNumber(@Param("accountNumber") String accountNumber);
 }
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientRepositoryWrapper.java
index f4231d1..b3e8e9e 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientRepositoryWrapper.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/ClientRepositoryWrapper.java
@@ -85,5 +85,12 @@
         this.context.validateAccessRights(client.getOffice().getHierarchy());
         return client;
     }
-
+    
+    public Client getClientByAccountNumber(String accountNumber){
+        Client client = this.repository.getClientByAccountNumber(accountNumber);
+        if(client==null){
+            throw  new ClientNotFoundException(accountNumber);
+        }
+        return client;
+    }
 }
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ClientNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ClientNotFoundException.java
index 7a6f034..1c182d4 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ClientNotFoundException.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/exception/ClientNotFoundException.java
@@ -28,4 +28,12 @@
     public ClientNotFoundException(final Long id) {
         super("error.msg.client.id.invalid", "Client with identifier " + id + " does not exist", id);
     }
+    
+    public ClientNotFoundException() {
+        super("error.msg.client.not.found.with.basic.details", "Client not found with basic details.");
+    }
+    
+    public ClientNotFoundException(String accountNumber) {
+        super("error.msg.client.not.found.with.account.number", "Client not found with account number "+accountNumber+".");
+    }
 }
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformServiceImpl.java
index 7751909..b5e8825 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/service/ClientReadPlatformServiceImpl.java
@@ -249,7 +249,7 @@
 
         if (displayName != null) {
             //extraCriteria += " and concat(ifnull(c.firstname, ''), if(c.firstname > '',' ', '') , ifnull(c.lastname, '')) like "
-        	paramList.add(ApiParameterHelper.sqlEncodeString(ApiParameterHelper.sqlEncodeString("%" + displayName + "%")));
+        	paramList.add("%" + displayName + "%");
         	extraCriteria += " and c.display_name like ? ";
         }
 
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/DisbursementData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/DisbursementData.java
index 97ab973..939a0ac 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/DisbursementData.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/DisbursementData.java
@@ -35,15 +35,17 @@
     @SuppressWarnings("unused")
     private final String loanChargeId;
     private final BigDecimal chargeAmount;
+    private final BigDecimal waivedChargeAmount;
 
     public DisbursementData(Long id, final LocalDate expectedDisbursementDate, final LocalDate actualDisbursementDate,
-            final BigDecimal principalDisbursed, final String loanChargeId, BigDecimal chargeAmount) {
+            final BigDecimal principalDisbursed, final String loanChargeId, BigDecimal chargeAmount, BigDecimal waivedChargeAmount) {
         this.id = id;
         this.expectedDisbursementDate = expectedDisbursementDate;
         this.actualDisbursementDate = actualDisbursementDate;
         this.principal = principalDisbursed;
         this.loanChargeId = loanChargeId;
         this.chargeAmount = chargeAmount;
+        this.waivedChargeAmount = waivedChargeAmount;
     }
 
     public LocalDate disbursementDate() {
@@ -83,4 +85,12 @@
         return target != null && target.isAfter(fromNotInclusive) && !target.isAfter(upToAndInclusive);
     }
 
+    
+    public BigDecimal getWaivedChargeAmount() {
+        if(this.waivedChargeAmount == null){
+            return BigDecimal.ZERO;
+        }
+        return this.waivedChargeAmount;
+    }
+
 }
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/RepaymentScheduleRelatedLoanData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/RepaymentScheduleRelatedLoanData.java
index 446ee3c..12bb038 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/RepaymentScheduleRelatedLoanData.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/RepaymentScheduleRelatedLoanData.java
@@ -75,6 +75,7 @@
     }
 
     public DisbursementData disbursementData() {
-        return new DisbursementData(null, this.expectedDisbursementDate, this.actualDisbursementDate, this.principal, null, null);
+        BigDecimal waivedChargeAmount = null;
+        return new DisbursementData(null, this.expectedDisbursementDate, this.actualDisbursementDate, this.principal, null, null, waivedChargeAmount);
     }
 }
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
index b71205e..480b081 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
@@ -5576,9 +5576,9 @@
             if (loanDisbursementDetails.actualDisbursementDate() != null) {
                 actualDisbursementDate = new LocalDate(loanDisbursementDetails.actualDisbursementDate());
             }
-
+            BigDecimal waivedChargeAmount = null;
             disbursementData.add(new DisbursementData(loanDisbursementDetails.getId(), expectedDisbursementDate, actualDisbursementDate,
-                    loanDisbursementDetails.principal(), null, null));
+                    loanDisbursementDetails.principal(), null, null, waivedChargeAmount));
         }
 
         return disbursementData;
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanDisbursementDetails.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanDisbursementDetails.java
index ef70a18..d19b60f 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanDisbursementDetails.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanDisbursementDetails.java
@@ -119,7 +119,8 @@
         if (this.actualDisbursementDate != null) {
             actualDisburseDate = new LocalDate(this.actualDisbursementDate);
         }
-        return new DisbursementData(getId(), expectedDisburseDate, actualDisburseDate, this.principal, null, null);
+        BigDecimal waivedChargeAmount = null;
+        return new DisbursementData(getId(), expectedDisburseDate, actualDisburseDate, this.principal, null, null, waivedChargeAmount);
     }
 
     public void updateActualDisbursementDate(Date actualDisbursementDate) {
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java
index cb42bf5..19a203a 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java
@@ -34,6 +34,7 @@
 import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
 import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
 import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.organisation.workingdays.data.AdjustedDateDetailsDTO;
 import org.apache.fineract.organisation.workingdays.domain.RepaymentRescheduleType;
 import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
 import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
@@ -121,7 +122,7 @@
         
         boolean isFirstRepayment = true;
         LocalDate firstRepaymentdate = this.scheduledDateGenerator.generateNextRepaymentDate(
-                loanApplicationTerms.getExpectedDisbursementDate(), loanApplicationTerms, isFirstRepayment, holidayDetailDTO);
+                loanApplicationTerms.getExpectedDisbursementDate(), loanApplicationTerms, isFirstRepayment);
         final LocalDate idealDisbursementDate = this.scheduledDateGenerator.idealDisbursementDateBasedOnFirstRepaymentDate(
                 loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery(), firstRepaymentdate,
                 loanApplicationTerms.getLoanCalendar(), loanApplicationTerms.getHolidayDetailDTO(), loanApplicationTerms);
@@ -179,10 +180,12 @@
         while (!scheduleParams.getOutstandingBalance().isZero() || !scheduleParams.getDisburseDetailMap().isEmpty()) {
             LocalDate previousRepaymentDate = scheduleParams.getActualRepaymentDate();
             scheduleParams.setActualRepaymentDate(this.scheduledDateGenerator.generateNextRepaymentDate(
-                    scheduleParams.getActualRepaymentDate(), loanApplicationTerms, isFirstRepayment, holidayDetailDTO));
+                    scheduleParams.getActualRepaymentDate(), loanApplicationTerms, isFirstRepayment));
+            AdjustedDateDetailsDTO adjustedDateDetailsDTO = this.scheduledDateGenerator.adjustRepaymentDate(
+                    scheduleParams.getActualRepaymentDate(), loanApplicationTerms, holidayDetailDTO);
+            scheduleParams.setActualRepaymentDate(adjustedDateDetailsDTO.getChangedActualRepaymentDate());
             isFirstRepayment = false;
-            LocalDate scheduledDueDate = this.scheduledDateGenerator.adjustRepaymentDate(scheduleParams.getActualRepaymentDate(),
-                    loanApplicationTerms, holidayDetailDTO);
+            LocalDate scheduledDueDate = adjustedDateDetailsDTO.getChangedScheduleDate();
             
             // calculated interest start date for the period
             LocalDate periodStartDateApplicableForInterest = calculateInterestStartDateForPeriod(loanApplicationTerms,
@@ -1320,7 +1323,7 @@
         do {
 
             params.setActualRepaymentDate(this.scheduledDateGenerator.generateNextRepaymentDate(params.getActualRepaymentDate(),
-                    loanApplicationTerms, isFirstRepayment, holidayDetailDTO));
+                    loanApplicationTerms, isFirstRepayment));
             if (params.getActualRepaymentDate().isAfter(currentDate)) {
                 params.setActualRepaymentDate(currentDate);
             }
@@ -2172,22 +2175,18 @@
                     ArrayList<LoanTermVariationsData> dueDateVariationsDataList = new ArrayList<>();
                     
 
-                 // check for date changes
-                   
+                   // check for date changes
+                    
                     do {
                         actualRepaymentDate = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate,
-                                loanApplicationTerms, isFirstRepayment, holidayDetailDTO);
+                                loanApplicationTerms, isFirstRepayment);
                         isFirstRepayment = false;
                         lastInstallmentDate = this.scheduledDateGenerator.adjustRepaymentDate(actualRepaymentDate, loanApplicationTerms,
-                                holidayDetailDTO);
+                                holidayDetailDTO).getChangedScheduleDate();
                         while (loanApplicationTerms.getLoanTermVariations().hasDueDateVariation(lastInstallmentDate)) {
                             LoanTermVariationsData variation = loanApplicationTerms.getLoanTermVariations().nextDueDateVariation();
                             if (!variation.isSpecificToInstallment()) {
                                 actualRepaymentDate = variation.getDateValue();
-                                /*if (!isDueDateChangeApplied) {
-                                    previousRepaymentDate = actualRepaymentDate;
-                                    isDueDateChangeApplied = true;
-                                }*/
                                 lastInstallmentDate = actualRepaymentDate;
                             }
                             dueDateVariationsDataList.add(variation);
@@ -2195,18 +2194,7 @@
                         loanTermVariationParams = applyExceptionLoanTermVariations(loanApplicationTerms, lastInstallmentDate,
                                 exceptionDataListIterator, instalmentNumber, totalCumulativePrincipal, totalCumulativeInterest, mc);
                     } while (loanTermVariationParams != null && loanTermVariationParams.isSkipPeriod());
-
-                    /*if (!lastInstallmentDate.isBefore(rescheduleFrom)) {
-                        actualRepaymentDate = previousRepaymentDate;
-                        if(isDueDateChangeApplied){
-                            int numberOfDateChangesApplied = dueDateVariationsDataList.size();
-                            while(numberOfDateChangesApplied >0 ){
-                                loanApplicationTerms.getLoanTermVariations().previousDueDateVariation();
-                                numberOfDateChangesApplied--;
-                            }
-                        }
-                        break;
-                    }*/
+                   
                     periodNumber++;
 
                     for (LoanTermVariationsData dueDateVariation : dueDateVariationsDataList) {
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java
index 93a58ce..5974d3f 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java
@@ -20,6 +20,7 @@
 
 import org.apache.fineract.organisation.holiday.domain.Holiday;
 import org.apache.fineract.organisation.holiday.service.HolidayUtil;
+import org.apache.fineract.organisation.workingdays.data.AdjustedDateDetailsDTO;
 import org.apache.fineract.organisation.workingdays.domain.RepaymentRescheduleType;
 import org.apache.fineract.organisation.workingdays.domain.WorkingDays;
 import org.apache.fineract.organisation.workingdays.service.WorkingDaysUtil;
@@ -45,25 +46,26 @@
         LocalDate lastRepaymentDate = loanApplicationTerms.getExpectedDisbursementDate();
         boolean isFirstRepayment = true;
         for (int repaymentPeriod = 1; repaymentPeriod <= numberOfRepayments; repaymentPeriod++) {
-            lastRepaymentDate = generateNextRepaymentDate(lastRepaymentDate, loanApplicationTerms, isFirstRepayment, holidayDetailDTO);
+            lastRepaymentDate = generateNextRepaymentDate(lastRepaymentDate, loanApplicationTerms, isFirstRepayment);
             isFirstRepayment = false;
         }
-        lastRepaymentDate = adjustRepaymentDate(lastRepaymentDate, loanApplicationTerms, holidayDetailDTO);
+        lastRepaymentDate = adjustRepaymentDate(lastRepaymentDate, loanApplicationTerms, holidayDetailDTO).getChangedScheduleDate();
         return lastRepaymentDate;
     }
 
     @Override
     public LocalDate generateNextRepaymentDate(final LocalDate lastRepaymentDate, final LoanApplicationTerms loanApplicationTerms,
-            boolean isFirstRepayment, final HolidayDetailDTO holidayDetailDTO) {
+            boolean isFirstRepayment) {
         final LocalDate firstRepaymentPeriodDate = loanApplicationTerms.getCalculatedRepaymentsStartingFromLocalDate();
         LocalDate dueRepaymentPeriodDate = null;
-        Calendar currentCalendar = loanApplicationTerms.getLoanCalendar();
         if (isFirstRepayment && firstRepaymentPeriodDate != null) {
             dueRepaymentPeriodDate = firstRepaymentPeriodDate;
         } else {
+                 LocalDate seedDate = null;
+             String reccuringString = null;
+            Calendar currentCalendar = loanApplicationTerms.getLoanCalendar();
             dueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms.getRepaymentPeriodFrequencyType(),
-                    loanApplicationTerms.getRepaymentEvery(), lastRepaymentDate, null,
-                    null);
+                    loanApplicationTerms.getRepaymentEvery(), lastRepaymentDate);
             dueRepaymentPeriodDate = CalendarUtils.adjustDate(dueRepaymentPeriodDate, loanApplicationTerms.getSeedDate(),
                     loanApplicationTerms.getRepaymentPeriodFrequencyType());
             if (currentCalendar != null) {
@@ -79,8 +81,6 @@
                 }
  
                 // get the start date from the calendar history
-                LocalDate seedDate = null;
-                String reccuringString = null;
                 if (calendarHistory == null) {
                     seedDate = currentCalendar.getStartDateLocalDate();
                     reccuringString = currentCalendar.getRecurrence();
@@ -89,80 +89,130 @@
                     reccuringString = calendarHistory.getRecurrence();
                 }
 
-                dueRepaymentPeriodDate = CalendarUtils.getNewRepaymentMeetingDate(reccuringString, seedDate, lastRepaymentDate.plusDays(1),
+                dueRepaymentPeriodDate = CalendarUtils.getNextRepaymentMeetingDate(reccuringString, seedDate, lastRepaymentDate,
                         loanApplicationTerms.getRepaymentEvery(),
                         CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(loanApplicationTerms.getLoanTermPeriodFrequencyType()),
-                        holidayDetailDTO.getWorkingDays(), loanApplicationTerms.isSkipRepaymentOnFirstDayofMonth(),
+                        loanApplicationTerms.isSkipRepaymentOnFirstDayofMonth(),
                         loanApplicationTerms.getNumberOfdays());
             }
         }
-        if (currentCalendar == null && holidayDetailDTO.getWorkingDays().getExtendTermForRepaymentsOnHolidays()) {
-            boolean repaymentDateIsOnHoliday = false;
-            // compile the list of holidays into Intervals to see if this date lands on a holiday
-            final List<Interval> holidayInterval = new ArrayList<>();
-            for (Holiday holiday : holidayDetailDTO.getHolidays()) {
-                holidayInterval.add(new Interval(
-                        holiday.getFromDateLocalDate().toDateTimeAtStartOfDay(),
-                        holiday.getToDateLocalDate().toDateTime(LocalTime.parse("23:59:59"))
-                ));
-            }
-            while (intervalListContainsDate(holidayInterval, dueRepaymentPeriodDate)) {
-                LocalDate previousDate = dueRepaymentPeriodDate;
-                dueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms.getRepaymentPeriodFrequencyType(),
-                        loanApplicationTerms.getRepaymentEvery(), dueRepaymentPeriodDate, loanApplicationTerms.getNthDay(),
-                        loanApplicationTerms.getWeekDayType());
-            }
-        }
+        
         return dueRepaymentPeriodDate;
     }
-    private boolean intervalListContainsDate(List<Interval> intervalList, LocalDate date) {
-        for (Interval interval : intervalList) {
-            if (interval.contains(date.toDateTime(LocalTime.parse("11:59:59")))) {
-                return true;
-            }
-        }
-        return false;
-    }
+
     @Override
-    public LocalDate adjustRepaymentDate(final LocalDate dueRepaymentPeriodDate, final LoanApplicationTerms loanApplicationTerms,
-            final HolidayDetailDTO holidayDetailDTO) {
+    public AdjustedDateDetailsDTO adjustRepaymentDate(final LocalDate dueRepaymentPeriodDate,
+            final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO) {
+        final LocalDate adjustedDate = dueRepaymentPeriodDate;
+        return getAdjustedDateDetailsDTO(dueRepaymentPeriodDate, loanApplicationTerms, holidayDetailDTO, adjustedDate);
+    }
 
-        LocalDate adjustedDate = dueRepaymentPeriodDate;
+    private AdjustedDateDetailsDTO getAdjustedDateDetailsDTO(final LocalDate dueRepaymentPeriodDate,
+            final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO, final LocalDate adjustedDate) {
+        final boolean isFirstRepayment = false;
+        final LocalDate nextRepaymentPeriodDueDate = generateNextRepaymentDate(adjustedDate, loanApplicationTerms, isFirstRepayment);
+        final AdjustedDateDetailsDTO newAdjustedDateDetailsDTO = new AdjustedDateDetailsDTO(adjustedDate, dueRepaymentPeriodDate,
+                nextRepaymentPeriodDueDate);
+        return recursivelyCheckNonWorkingDaysAndHolidaysAndWorkingDaysExemptionToGenerateNextRepaymentPeriodDate(newAdjustedDateDetailsDTO,
+                loanApplicationTerms, holidayDetailDTO, isFirstRepayment);
+    }
 
-        LocalDate nextDueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms.getRepaymentPeriodFrequencyType(),
-                loanApplicationTerms.getRepaymentEvery(), adjustedDate, loanApplicationTerms.getNthDay(),
-                loanApplicationTerms.getWeekDayType());
-
-        final RepaymentRescheduleType rescheduleType = RepaymentRescheduleType.fromInt(holidayDetailDTO.getWorkingDays()
-                .getRepaymentReschedulingType());
+    /**
+     * Recursively checking non working days and holidays and working days
+     * exemption to generate next repayment period date Base on the
+     * configuration
+     * 
+     * @param adjustedDateDetailsDTO
+     * @param loanApplicationTerms
+     * @param holidayDetailDTO
+     * @param nextRepaymentPeriodDueDate
+     * @param rescheduleType
+     * @param isFirstRepayment
+     * @return
+     */
+    private AdjustedDateDetailsDTO recursivelyCheckNonWorkingDaysAndHolidaysAndWorkingDaysExemptionToGenerateNextRepaymentPeriodDate(
+            final AdjustedDateDetailsDTO adjustedDateDetailsDTO, final LoanApplicationTerms loanApplicationTerms,
+            final HolidayDetailDTO holidayDetailDTO, final boolean isFirstRepayment) {
+        
+        checkAndUpdateWorkingDayIfRepaymentDateIsNonWorkingDay(adjustedDateDetailsDTO, holidayDetailDTO, loanApplicationTerms,
+                isFirstRepayment);
+        
+        checkAndUpdateWorkingDayIfRepaymentDateIsHolidayDay(adjustedDateDetailsDTO, holidayDetailDTO, loanApplicationTerms,
+                isFirstRepayment);
 
         /**
-         * Fix for https://mifosforge.jira.com/browse/MIFOSX-1357
+         * Check Changed Schedule Date is holiday or is not a working day Then
+         * re-call this method to get the non holiday and working day
          */
-        // recursively check for the next working meeting day.
-        while (WorkingDaysUtil.isNonWorkingDay(holidayDetailDTO.getWorkingDays(), nextDueRepaymentPeriodDate)
-                && rescheduleType == RepaymentRescheduleType.MOVE_TO_NEXT_REPAYMENT_MEETING_DAY) {
-
-            nextDueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms.getRepaymentPeriodFrequencyType(),
-                    loanApplicationTerms.getRepaymentEvery(), nextDueRepaymentPeriodDate, loanApplicationTerms.getNthDay(),
-                    loanApplicationTerms.getWeekDayType());
-            nextDueRepaymentPeriodDate = CalendarUtils.adjustDate(nextDueRepaymentPeriodDate, loanApplicationTerms.getSeedDate(),
-                    loanApplicationTerms.getRepaymentPeriodFrequencyType());
-
+        if ((holidayDetailDTO.isHolidayEnabled() && HolidayUtil.getApplicableHoliday(adjustedDateDetailsDTO.getChangedScheduleDate(),
+                holidayDetailDTO.getHolidays()) != null)
+                || WorkingDaysUtil.isNonWorkingDay(holidayDetailDTO.getWorkingDays(), adjustedDateDetailsDTO.getChangedScheduleDate())) {
+            recursivelyCheckNonWorkingDaysAndHolidaysAndWorkingDaysExemptionToGenerateNextRepaymentPeriodDate(adjustedDateDetailsDTO,
+                    loanApplicationTerms, holidayDetailDTO, isFirstRepayment);
         }
-        adjustedDate = WorkingDaysUtil.getOffSetDateIfNonWorkingDay(adjustedDate, nextDueRepaymentPeriodDate,
-                holidayDetailDTO.getWorkingDays());
+        return adjustedDateDetailsDTO;
+    } 
 
+    /**
+     * This method to check and update the working day if repayment date is
+     * holiday
+     * 
+     * @param adjustedDateDetailsDTO
+     * @param holidayDetailDTO
+     * @param loanApplicationTerms
+     * @param isFirstRepayment
+     */
+    private void checkAndUpdateWorkingDayIfRepaymentDateIsHolidayDay(final AdjustedDateDetailsDTO adjustedDateDetailsDTO,
+            final HolidayDetailDTO holidayDetailDTO, final LoanApplicationTerms loanApplicationTerms, final boolean isFirstRepayment) {
         if (holidayDetailDTO.isHolidayEnabled()) {
-            adjustedDate = HolidayUtil.getRepaymentRescheduleDateToIfHoliday(adjustedDate, holidayDetailDTO.getHolidays());
+            Holiday applicableHolidayForNewAdjustedDate = null;
+            while ((applicableHolidayForNewAdjustedDate = HolidayUtil.getApplicableHoliday(adjustedDateDetailsDTO.getChangedScheduleDate(),
+                    holidayDetailDTO.getHolidays())) != null) {
+                if (applicableHolidayForNewAdjustedDate.getReScheduleType().isResheduleToNextRepaymentDate()) {
+                    LocalDate nextRepaymentPeriodDueDate = adjustedDateDetailsDTO.getChangedActualRepaymentDate();
+                    while (!nextRepaymentPeriodDueDate.isAfter(adjustedDateDetailsDTO.getChangedScheduleDate())) {
+                        nextRepaymentPeriodDueDate = generateNextRepaymentDate(nextRepaymentPeriodDueDate, loanApplicationTerms,
+                                isFirstRepayment);
+                    }
+                    adjustedDateDetailsDTO.setChangedScheduleDate(nextRepaymentPeriodDueDate);
+                    adjustedDateDetailsDTO.setNextRepaymentPeriodDueDate(nextRepaymentPeriodDueDate);
+                    adjustedDateDetailsDTO.setChangedActualRepaymentDate(adjustedDateDetailsDTO.getChangedScheduleDate());
+                } else {
+                    HolidayUtil.updateRepaymentRescheduleDateToWorkingDayIfItIsHoliday(adjustedDateDetailsDTO,
+                            applicableHolidayForNewAdjustedDate);
+                }
+            }
         }
+    }
 
-        return adjustedDate;
+    /**
+     * This method to check and update the working day if repayment date is non
+     * working day
+     * 
+     * @param adjustedDateDetailsDTO
+     * @param holidayDetailDTO
+     * @param isFirstRepayment 
+     * @param loanApplicationTerms 
+     */
+    private void checkAndUpdateWorkingDayIfRepaymentDateIsNonWorkingDay(final AdjustedDateDetailsDTO adjustedDateDetailsDTO,
+            final HolidayDetailDTO holidayDetailDTO, final LoanApplicationTerms loanApplicationTerms, final boolean isFirstRepayment) {
+        while (WorkingDaysUtil.isNonWorkingDay(holidayDetailDTO.getWorkingDays(), adjustedDateDetailsDTO.getChangedScheduleDate())) {
+            if (WorkingDaysUtil.getRepaymentRescheduleType(holidayDetailDTO.getWorkingDays(),
+                    adjustedDateDetailsDTO.getChangedScheduleDate()).isMoveToNextRepaymentDay()) {
+                while (WorkingDaysUtil.isNonWorkingDay(holidayDetailDTO.getWorkingDays(),
+                        adjustedDateDetailsDTO.getNextRepaymentPeriodDueDate())
+                        || adjustedDateDetailsDTO.getChangedScheduleDate().isAfter(adjustedDateDetailsDTO.getNextRepaymentPeriodDueDate())) {
+                    final LocalDate nextRepaymentPeriodDueDate = generateNextRepaymentDate(
+                            adjustedDateDetailsDTO.getNextRepaymentPeriodDueDate(), loanApplicationTerms, isFirstRepayment);
+                    adjustedDateDetailsDTO.setNextRepaymentPeriodDueDate(nextRepaymentPeriodDueDate);
+                }
+            }
+            WorkingDaysUtil.updateWorkingDayIfRepaymentDateIsNonWorkingDay(adjustedDateDetailsDTO, holidayDetailDTO.getWorkingDays());
+        }
     }
 
     @Override
-    public LocalDate getRepaymentPeriodDate(final PeriodFrequencyType frequency, final int repaidEvery, final LocalDate startDate,
-            Integer nthDay, DayOfWeekType dayOfWeek) {
+    public LocalDate getRepaymentPeriodDate(final PeriodFrequencyType frequency, final int repaidEvery, final LocalDate startDate) {
         LocalDate dueRepaymentPeriodDate = startDate;
         switch (frequency) {
             case DAYS:
@@ -173,9 +223,6 @@
             break;
             case MONTHS:
                 dueRepaymentPeriodDate = startDate.plusMonths(repaidEvery);
-                if (!(nthDay == null || dayOfWeek == null || dayOfWeek == DayOfWeekType.INVALID)) {
-                    dueRepaymentPeriodDate = adjustToNthWeekDay(dueRepaymentPeriodDate, nthDay, dayOfWeek.getValue());
-                }
             break;
             case YEARS:
                 dueRepaymentPeriodDate = startDate.plusYears(repaidEvery);
@@ -282,10 +329,10 @@
         LocalDate generatedDate = loanApplicationTerms.getExpectedDisbursementDate();
         boolean isFirstRepayment = true;
         while (!generatedDate.isAfter(lastRepaymentDate)) {
-            generatedDate = generateNextRepaymentDate(generatedDate, loanApplicationTerms, isFirstRepayment, holidayDetailDTO);
+            generatedDate = generateNextRepaymentDate(generatedDate, loanApplicationTerms, isFirstRepayment);
             isFirstRepayment = false;
         }
-        generatedDate = adjustRepaymentDate(generatedDate, loanApplicationTerms, holidayDetailDTO);
+        generatedDate = adjustRepaymentDate(generatedDate, loanApplicationTerms, holidayDetailDTO).getChangedScheduleDate();
         return generatedDate;
     }
 }
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
index 8db5aff..d5f08cb 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java
@@ -183,7 +183,7 @@
 
     private Money adjustPrincipalForFlatLoans;
 
-    private final LocalDate seedDate;
+    private LocalDate seedDate;
 
     private final CalendarHistoryDataWrapper calendarHistoryDataWrapper;
 
@@ -1742,5 +1742,9 @@
     public void updateTotalInterestAccounted(Money totalInterestAccounted){
         this.totalInterestAccounted = totalInterestAccounted;
     }
+
+    public void setSeedDate(LocalDate seedDate) {
+        this.seedDate = seedDate;
+    }
     
 }
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java
index c82301d..2119472 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java
@@ -18,6 +18,7 @@
  */
 package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
 
+import org.apache.fineract.organisation.workingdays.data.AdjustedDateDetailsDTO;
 import org.apache.fineract.portfolio.calendar.domain.Calendar;
 import org.apache.fineract.portfolio.common.domain.DayOfWeekType;
 import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
@@ -32,14 +33,12 @@
             final LocalDate firstRepaymentDate, final Calendar loanCalendar, final HolidayDetailDTO holidayDetailDTO,
             final LoanApplicationTerms loanApplicationTerms);
 
-    LocalDate generateNextRepaymentDate(LocalDate lastRepaymentDate, LoanApplicationTerms loanApplicationTerms, boolean isFirstRepayment,
+    LocalDate generateNextRepaymentDate(LocalDate lastRepaymentDate, LoanApplicationTerms loanApplicationTerms, boolean isFirstRepayment);
+
+    AdjustedDateDetailsDTO  adjustRepaymentDate(LocalDate dueRepaymentPeriodDate, LoanApplicationTerms loanApplicationTerms,
             final HolidayDetailDTO holidayDetailDTO);
 
-    LocalDate adjustRepaymentDate(LocalDate dueRepaymentPeriodDate, LoanApplicationTerms loanApplicationTerms,
-            final HolidayDetailDTO holidayDetailDTO);
-
-    LocalDate getRepaymentPeriodDate(PeriodFrequencyType frequency, int repaidEvery, LocalDate startDate, Integer nthDay,
-            DayOfWeekType dayOfWeek);
+    LocalDate getRepaymentPeriodDate(PeriodFrequencyType frequency, int repaidEvery, LocalDate startDate);
 
     Boolean isDateFallsInSchedule(PeriodFrequencyType frequency, int repaidEvery, LocalDate startDate, LocalDate date);
 
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java
index a335e9c..095da8a 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java
@@ -515,7 +515,8 @@
                             && StringUtils.isNotBlank((jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).getAsString()))) {
                         principal = jsonObject.getAsJsonPrimitive(LoanApiConstants.disbursementPrincipalParameterName).getAsBigDecimal();
                     }
-                    disbursementDatas.add(new DisbursementData(null, expectedDisbursementDate, null, principal, null, null));
+                    BigDecimal waivedChargeAmount = null;
+                    disbursementDatas.add(new DisbursementData(null, expectedDisbursementDate, null, principal, null, null, waivedChargeAmount));
                     i++;
                 } while (i < disbursementDataArray.size());
             }
@@ -536,7 +537,7 @@
 
     public void validateDisbursementDateWithMeetingDates(final LocalDate expectedDisbursementDate, final Calendar calendar, Boolean isSkipRepaymentOnFirstMonth, Integer numberOfDays) {
         // disbursement date should fall on a meeting date
-        if (!calendar.isValidRecurringDate(expectedDisbursementDate, isSkipRepaymentOnFirstMonth, numberOfDays)) {
+        if (calendar != null && !calendar.isValidRecurringDate(expectedDisbursementDate, isSkipRepaymentOnFirstMonth, numberOfDays)) {
             final String errorMessage = "Expected disbursement date '" + expectedDisbursementDate + "' do not fall on a meeting date";
             throw new LoanApplicationDateException("disbursement.date.do.not.match.meeting.date", errorMessage, expectedDisbursementDate);
         }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java
index e5ab38d..ef64ec3 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java
@@ -114,10 +114,10 @@
         for (LoanTermVariationsData loanTermVariation : loanApplicationTerms.getLoanTermVariations().getDueDateVariation()) {
             if (rescheduleFromDate.isBefore(loanTermVariation.getTermApplicableFrom())) {
                 LocalDate applicableDate = this.scheduledDateGenerator.generateNextRepaymentDate(rescheduleFromDate, loanApplicationTerms,
-                        false, loanApplicationTerms.getHolidayDetailDTO());
+                        false);
                 if (loanTermVariation.getTermApplicableFrom().equals(applicableDate)) {
                     LocalDate adjustedDate = this.scheduledDateGenerator.generateNextRepaymentDate(adjustedApplicableDate,
-                            loanApplicationTerms, false, loanApplicationTerms.getHolidayDetailDTO());
+                            loanApplicationTerms, false);
                     loanTermVariation.setApplicableFromDate(adjustedDate);
                     loanTermVariationsData.add(loanTermVariation);
                 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java
index 977e7ba..ebffa27 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java
@@ -410,9 +410,9 @@
                     } else if (!activeLoanTermVariation.fetchTermApplicaDate().isBefore(fromScheduleDate)) {
                         while (currentScheduleDate.isBefore(activeLoanTermVariation.fetchTermApplicaDate())) {
                             currentScheduleDate = this.scheduledDateGenerator.generateNextRepaymentDate(currentScheduleDate,
-                                    loanApplicationTerms, false, loanApplicationTerms.getHolidayDetailDTO());
+                                    loanApplicationTerms, false);
                             modifiedScheduleDate = this.scheduledDateGenerator.generateNextRepaymentDate(modifiedScheduleDate,
-                                    loanApplicationTerms, false, loanApplicationTerms.getHolidayDetailDTO());
+                                    loanApplicationTerms, false);
                             changeMap.put(currentScheduleDate, modifiedScheduleDate);
                         }
                         if (changeMap.containsKey(activeLoanTermVariation.fetchTermApplicaDate())) {
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java
index 9fdde8f..cf7b641 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java
@@ -51,6 +51,7 @@
 import org.joda.time.Days;
 import org.joda.time.LocalDate;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataAccessException;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -266,7 +267,7 @@
 
     private void addAccrualAccounting(LoanScheduleAccrualData scheduleAccrualData, BigDecimal amount, BigDecimal interestportion,
             BigDecimal totalAccInterest, BigDecimal feeportion, BigDecimal totalAccFee, BigDecimal penaltyportion,
-            BigDecimal totalAccPenalty, final LocalDate accruedTill) throws Exception {
+            BigDecimal totalAccPenalty, final LocalDate accruedTill) throws DataAccessException {
         String transactionSql = "INSERT INTO m_loan_transaction  (loan_id,office_id,is_reversed,transaction_type_enum,transaction_date,amount,interest_portion_derived,"
                 + "fee_charges_portion_derived,penalty_charges_portion_derived, submitted_on_date) VALUES (?, ?, 0, ?, ?, ?, ?, ?, ?, ?)";
         this.jdbcTemplate.update(transactionSql, scheduleAccrualData.getLoanId(), scheduleAccrualData.getOfficeId(),
@@ -485,7 +486,7 @@
 
     @Override
     @Transactional
-    public void addIncomeAndAccrualTransactions(Long loanId) throws Exception {
+    public void addIncomeAndAccrualTransactions(Long loanId) throws LoanNotFoundException {
         if (loanId != null) {
             Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId, true);
             if (loan == null) { throw new LoanNotFoundException(loanId); }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java
index 8757e01..3b14a92 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java
@@ -1086,7 +1086,11 @@
         if (loan.isSyncDisbursementWithMeeting() && (loan.isGroupLoan() || loan.isJLGLoan())) {
             final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
                     CalendarEntityType.LOANS.getValue());
-            final Calendar calendar = calendarInstance.getCalendar();
+            Calendar calendar = null;
+            if (calendarInstance != null) {
+                calendar = calendarInstance.getCalendar();
+            }
+           // final Calendar calendar = calendarInstance.getCalendar();
             boolean isSkipRepaymentOnFirstMonthEnabled = this.configurationDomainService.isSkippingMeetingOnFirstDayOfMonthEnabled();
             if (isSkipRepaymentOnFirstMonthEnabled) {
                 isSkipRepaymentOnFirstMonth = this.loanUtilService.isLoanRepaymentsSyncWithMeeting(loan.group(), calendar);
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
index 19ea4ae..ab6c310 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
@@ -62,7 +62,6 @@
 import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType;
 import org.apache.fineract.portfolio.calendar.service.CalendarReadPlatformService;
 import org.apache.fineract.portfolio.charge.data.ChargeData;
-import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
 import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
 import org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService;
 import org.apache.fineract.portfolio.client.data.ClientData;
@@ -93,8 +92,6 @@
 import org.apache.fineract.portfolio.loanaccount.data.RepaymentScheduleRelatedLoanData;
 import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
 import org.apache.fineract.portfolio.loanaccount.domain.Loan;
-import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
-import org.apache.fineract.portfolio.loanaccount.domain.LoanInstallmentCharge;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
@@ -1056,7 +1053,10 @@
 
         @Override
         public LoanScheduleData extractData(final ResultSet rs) throws SQLException, DataAccessException {
-
+            BigDecimal waivedChargeAmount = BigDecimal.ZERO;
+            for (DisbursementData disbursementDetail : disbursementData) {
+                waivedChargeAmount = waivedChargeAmount.add(disbursementDetail.getWaivedChargeAmount());
+            }
             final LoanSchedulePeriodData disbursementPeriod = LoanSchedulePeriodData.disbursementOnlyPeriod(
                     this.disbursement.disbursementDate(), this.disbursement.amount(), this.totalFeeChargesDueAtDisbursement,
                     this.disbursement.isDisbursed());
@@ -1095,9 +1095,9 @@
             Money totalOutstanding = Money.zero(monCurrency);
 
             // update totals with details of fees charged during disbursement
-            totalFeeChargesCharged = totalFeeChargesCharged.plus(disbursementPeriod.feeChargesDue());
-            totalRepaymentExpected = totalRepaymentExpected.plus(disbursementPeriod.feeChargesDue());
-            totalRepayment = totalRepayment.plus(disbursementPeriod.feeChargesPaid());
+            totalFeeChargesCharged = totalFeeChargesCharged.plus(disbursementPeriod.feeChargesDue().subtract(waivedChargeAmount));
+            totalRepaymentExpected = totalRepaymentExpected.plus(disbursementPeriod.feeChargesDue()).minus(waivedChargeAmount);
+            totalRepayment = totalRepayment.plus(disbursementPeriod.feeChargesPaid()).minus(waivedChargeAmount);
             totalOutstanding = totalOutstanding.plus(disbursementPeriod.feeChargesDue()).minus(disbursementPeriod.feeChargesPaid());
 
             Integer loanTermInDays = Integer.valueOf(0);
@@ -1121,7 +1121,7 @@
                                             disbursementChargeAmount, data.isDisbursed());
                                 } else {
                                     periodData = LoanSchedulePeriodData.disbursementOnlyPeriod(data.disbursementDate(), data.amount(),
-                                            disbursementChargeAmount.add(data.getChargeAmount()), data.isDisbursed());
+                                            disbursementChargeAmount.add(data.getChargeAmount()).subtract(waivedChargeAmount), data.isDisbursed());
                                 }
                                 if (periodData != null) {
                                     periods.add(periodData);
@@ -1543,7 +1543,7 @@
             final BigDecimal waivedAmount = rs.getBigDecimal("waivedAmount");
             if (chargeAmount != null && waivedAmount != null) chargeAmount = chargeAmount.subtract(waivedAmount);
             final DisbursementData disbursementData = new DisbursementData(id, expectedDisbursementdate, actualDisbursementdate, principal,
-                    loanChargeId, chargeAmount);
+                    loanChargeId, chargeAmount, waivedAmount);
             return disbursementData;
         }
 
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
index 98f86fc..5744b67 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
@@ -2421,7 +2421,7 @@
             while (!startDate.isAfter(DateUtils.getLocalDateOfTenant())) {
                 scheduleDates.put(frequencyNunber++, startDate.minusDays(diff.intValue()));
                 LocalDate scheduleDate = scheduledDateGenerator.getRepaymentPeriodDate(PeriodFrequencyType.fromInt(feeFrequency),
-                        chargeDefinition.feeInterval(), startDate, null, null);
+                        chargeDefinition.feeInterval(), startDate);
 
                 startDate = scheduleDate;
             }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/DepositsApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/DepositsApiConstants.java
index c886128..a84a2b3 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/DepositsApiConstants.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/DepositsApiConstants.java
@@ -151,6 +151,7 @@
     public static final String runningBalanceParamName = "runningBalance";
     public static final String reversedParamName = "reversed";
     public static final String dateParamName = "date";
+    public static final String accountIdParamName = "accountId";
 
     // recurring deposits update parameters
     public static final String effectiveDateParamName = "effectiveDate";
@@ -199,9 +200,6 @@
     // template
     public static final String chartTemplate = "chartTemplate";
 
-    // allowed column names for sorting the query result
-    public final static Set<String> supportedOrderByValues = new HashSet<>(Arrays.asList("id", "accountNumbr", "officeId", "officeName"));
-
     /**
      * Deposit Product Parameters
      */
@@ -244,7 +242,7 @@
             adjustAdvanceTowardsFuturePaymentsParamName, recurringFrequencyTypeParamName, recurringFrequencyParamName,
             isCalendarInheritedParamName));
 
-    public static final Set<String> DEPOSIT_PRECLOSURE_CALCULATION_REQUEST_DATA_PARAMETERS = new HashSet<>(
+    private static final Set<String> DEPOSIT_PRECLOSURE_CALCULATION_REQUEST_DATA_PARAMETERS = new HashSet<>(
             Arrays.asList(preMatureCloseOnDateParamName));
 
     public static final Set<String> FIXED_DEPOSIT_PRODUCT_REQUEST_DATA_PARAMETERS = fixedDepositProductRequestData();
@@ -293,13 +291,15 @@
      * Depost Account parameters
      */
 
-    public static final Set<String> DEPOSIT_ACCOUNT_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
-            dateFormatParamName, monthDayFormatParamName, accountNoParamName, externalIdParamName, clientIdParamName, groupIdParamName,
-            productIdParamName, fieldOfficerIdParamName, submittedOnDateParamName, nominalAnnualInterestRateParamName,
-            interestCompoundingPeriodTypeParamName, interestPostingPeriodTypeParamName, interestCalculationTypeParamName,
-            interestCalculationDaysInYearTypeParamName, lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName,
-            chargesParamName, chartsParamName, depositAmountParamName, depositPeriodParamName, depositPeriodFrequencyIdParamName,
-            savingsAccounts, expectedFirstDepositOnDateParamName, SavingsApiConstants.withHoldTaxParamName));
+	private static final Set<String> DEPOSIT_ACCOUNT_REQUEST_DATA_PARAMETERS = new HashSet<>(
+			Arrays.asList(localeParamName, dateFormatParamName, monthDayFormatParamName, accountNoParamName,
+					externalIdParamName, clientIdParamName, groupIdParamName, productIdParamName,
+					fieldOfficerIdParamName, submittedOnDateParamName, nominalAnnualInterestRateParamName,
+					interestCompoundingPeriodTypeParamName, interestPostingPeriodTypeParamName,
+					interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName,
+					lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName, chargesParamName,
+					chartsParamName, depositAmountParamName, depositPeriodParamName, depositPeriodFrequencyIdParamName,
+					savingsAccounts, expectedFirstDepositOnDateParamName, SavingsApiConstants.withHoldTaxParamName));
 
     public static final Set<String> FIXED_DEPOSIT_ACCOUNT_REQUEST_DATA_PARAMETERS = fixedDepositAccountRequestData();
     public static final Set<String> FIXED_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS = fixedDepositAccountResponseData();
@@ -345,27 +345,27 @@
         return recurringDepositResponseData;
     }
 
-    public static final Set<String> FIXED_DEPOSIT_TRANSACTION_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(idParamName,
-            "accountId", accountNoParamName, "currency", "amount", dateParamName, paymentDetailDataParamName, runningBalanceParamName,
-            reversedParamName));
+	private static final Set<String> RECURRING_DEPOSIT_TRANSACTION_RESPONSE_DATA_PARAMETERS = new HashSet<>(
+			Arrays.asList(idParamName, "accountId", accountNoParamName, "currency", "amount", dateParamName,
+					paymentDetailDataParamName, runningBalanceParamName, reversedParamName));
 
-    public static final Set<String> RECURRING_DEPOSIT_TRANSACTION_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(idParamName,
-            "accountId", accountNoParamName, "currency", "amount", dateParamName, paymentDetailDataParamName, runningBalanceParamName,
-            reversedParamName));
-
-    public static final Set<String> SAVINGS_ACCOUNT_ACTIVATION_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName,
+    private static final Set<String> SAVINGS_ACCOUNT_ACTIVATION_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList
+            (localeParamName,
             dateFormatParamName, activatedOnDateParamName));
 
-    public static final Set<String> SAVINGS_ACCOUNT_CHARGES_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(chargeIdParamName,
+    private static final Set<String> SAVINGS_ACCOUNT_CHARGES_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList
+            (chargeIdParamName,
             savingsAccountChargeIdParamName, chargeNameParamName, penaltyParamName, chargeTimeTypeParamName, dueAsOfDateParamName,
             chargeCalculationTypeParamName, percentageParamName, amountPercentageAppliedToParamName, currencyParamName,
             amountWaivedParamName, amountWrittenOffParamName, amountOutstandingParamName, amountOrPercentageParamName, amountParamName,
             amountPaidParamName, chargeOptionsParamName));
 
-    public static final Set<String> SAVINGS_ACCOUNT_CHARGES_ADD_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(chargeIdParamName,
+    private static final Set<String> SAVINGS_ACCOUNT_CHARGES_ADD_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList
+            (chargeIdParamName,
             amountParamName, dueAsOfDateParamName, dateFormatParamName, localeParamName, feeOnMonthDayParamName, monthDayFormatParamName,
             feeIntervalParamName));
 
-    public static final Set<String> SAVINGS_ACCOUNT_CHARGES_PAY_CHARGE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(
+    private static final Set<String> SAVINGS_ACCOUNT_CHARGES_PAY_CHARGE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays
+            .asList(
             amountParamName, dueAsOfDateParamName, dateFormatParamName, localeParamName));
 }
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsApiConstants.java
index 1b8aa53..1140c4d 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsApiConstants.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsApiConstants.java
@@ -26,7 +26,7 @@
 import org.apache.fineract.portfolio.savings.data.SavingsAccountData;
 import org.apache.fineract.portfolio.savings.data.SavingsProductData;
 
-public class SavingsApiConstants {
+public class    SavingsApiConstants {
 
     public static final String SAVINGS_PRODUCT_RESOURCE_NAME = "savingsproduct";
     public static final String SAVINGS_ACCOUNT_RESOURCE_NAME = "savingsaccount";
@@ -133,6 +133,7 @@
     public static final String minBalanceForInterestCalculationParamName = "minBalanceForInterestCalculation";
     public static final String withdrawBalanceParamName = "withdrawBalance";
     public static final String onHoldFundsParamName = "onHoldFunds";
+    public static final String savingsAmountOnHold = "savingsAmountOnHold";
     public static final String withHoldTaxParamName = "withHoldTax";
     public static final String taxGroupIdParamName = "taxGroupId";
 
@@ -186,7 +187,4 @@
     public static final String datatables = "datatables";
     
     public static final String ERROR_MSG_SAVINGS_ACCOUNT_NOT_ACTIVE = "not.in.active.state";
-    
-    public static final Set<String> SAVINGS_ACCOUNT_HOLD_AMOUNT_REQUEST_DATA_PARAMETERS = new HashSet<>(
-            Arrays.asList(transactionDateParamName, dateFormatParamName, localeParamName, transactionAmountParamName));
 }
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountTransactionsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountTransactionsApiResource.java
index fadf019..d0785f9 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountTransactionsApiResource.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountTransactionsApiResource.java
@@ -18,7 +18,10 @@
  */
 package org.apache.fineract.portfolio.savings.api;
 
+import java.util.Arrays;
 import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
 
 import javax.ws.rs.Consumes;
 import javax.ws.rs.GET;
@@ -62,7 +65,13 @@
     private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
     private final ApiRequestParameterHelper apiRequestParameterHelper;
     private final SavingsAccountReadPlatformService savingsAccountReadPlatformService;
-    private final PaymentTypeReadPlatformService paymentTypeReadPlatformService;
+	private final PaymentTypeReadPlatformService paymentTypeReadPlatformService;
+	private static final Set<String> FIXED_DEPOSIT_TRANSACTION_RESPONSE_DATA_PARAMETERS = new HashSet<>(
+			Arrays.asList(DepositsApiConstants.idParamName, DepositsApiConstants.accountIdParamName,
+					DepositsApiConstants.accountNoParamName, DepositsApiConstants.currencyParamName,
+					DepositsApiConstants.amountParamName, DepositsApiConstants.dateParamName,
+					DepositsApiConstants.paymentDetailDataParamName, DepositsApiConstants.runningBalanceParamName,
+					DepositsApiConstants.reversedParamName));
 
     @Autowired
     public FixedDepositAccountTransactionsApiResource(final PlatformSecurityContext context,
@@ -120,8 +129,8 @@
             transactionData = SavingsAccountTransactionData.templateOnTop(transactionData, paymentTypeOptions);
         }
 
-        return this.toApiJsonSerializer.serialize(settings, transactionData,
-                DepositsApiConstants.FIXED_DEPOSIT_TRANSACTION_RESPONSE_DATA_PARAMETERS);
+		return this.toApiJsonSerializer.serialize(settings, transactionData,
+				FIXED_DEPOSIT_TRANSACTION_RESPONSE_DATA_PARAMETERS);
     }
 
     @POST
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountTransactionsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountTransactionsApiResource.java
index 24353c7..089dc2b 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountTransactionsApiResource.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountTransactionsApiResource.java
@@ -18,7 +18,10 @@
  */
 package org.apache.fineract.portfolio.savings.api;
 
+import java.util.Arrays;
 import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
 
 import javax.ws.rs.Consumes;
 import javax.ws.rs.GET;
@@ -65,6 +68,12 @@
     private final SavingsAccountReadPlatformService savingsAccountReadPlatformService;
     private final DepositAccountReadPlatformService depositAccountReadPlatformService;
     private final PaymentTypeReadPlatformService paymentTypeReadPlatformService;
+    private static final Set<String> FIXED_DEPOSIT_TRANSACTION_RESPONSE_DATA_PARAMETERS = new HashSet<>(
+            Arrays.asList(DepositsApiConstants.idParamName, DepositsApiConstants.accountIdParamName,
+                    DepositsApiConstants.accountNoParamName, DepositsApiConstants.currencyParamName,
+                    DepositsApiConstants.amountParamName, DepositsApiConstants.dateParamName,
+                    DepositsApiConstants.paymentDetailDataParamName, DepositsApiConstants.runningBalanceParamName,
+                    DepositsApiConstants.reversedParamName));
 
     @Autowired
     public RecurringDepositAccountTransactionsApiResource(final PlatformSecurityContext context,
@@ -141,8 +150,8 @@
             transactionData = SavingsAccountTransactionData.templateOnTop(transactionData, paymentTypeOptions);
         }
 
-        return this.toApiJsonSerializer.serialize(settings, transactionData,
-                DepositsApiConstants.FIXED_DEPOSIT_TRANSACTION_RESPONSE_DATA_PARAMETERS);
+		return this.toApiJsonSerializer.serialize(settings, transactionData,
+				FIXED_DEPOSIT_TRANSACTION_RESPONSE_DATA_PARAMETERS);
     }
 
     @POST
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsApiSetConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsApiSetConstants.java
index 70ac7b4..36d319a 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsApiSetConstants.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsApiSetConstants.java
@@ -65,7 +65,7 @@
 			"interestCalculationTypeOptions", "interestCalculationDaysInYearTypeOptions",
 			"lockinPeriodFrequencyTypeOptions", "withdrawalFeeTypeOptions", "withdrawalFee", "annualFee",
 			onHoldFundsParamName, nominalAnnualInterestRateOverdraftParamName,
-			minOverdraftForInterestCalculationParamName, datatables));
+			minOverdraftForInterestCalculationParamName, datatables, savingsAmountOnHold));
 
 	protected static final Set<String> SAVINGS_TRANSACTION_RESPONSE_DATA_PARAMETERS = new HashSet<>(
 			Arrays.asList(idParamName, "accountId", accountNoParamName, "currency", "amount", dateParamName,
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountData.java
index d06bbe9..32a0630 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountData.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountData.java
@@ -76,6 +76,7 @@
     private final Integer daysToInactive;
     private final Integer daysToDormancy;
     private final Integer daysToEscheat;
+    private final BigDecimal savingsAmountOnHold;
 
     // associations
     private final SavingsAccountSummaryData summary;
@@ -118,7 +119,7 @@
             final BigDecimal onHoldFunds, final BigDecimal nominalAnnualInterestRateOverdraft,
             final BigDecimal minOverdraftForInterestCalculation, final boolean withHoldTax, final TaxGroupData taxGroup, 
             final LocalDate lastActiveTransactionDate, final boolean isDormancyTrackingActive, final Integer daysToInactive, 
-            final Integer daysToDormancy, final Integer daysToEscheat) {
+            final Integer daysToDormancy, final Integer daysToEscheat, final BigDecimal savingsAmountOnHold) {
 
         final Collection<SavingsProductData> productOptions = null;
         final Collection<StaffData> fieldOfficerOptions = null;
@@ -142,7 +143,7 @@
                 lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges, chargeOptions, allowOverdraft, overdraftLimit,
                 minRequiredBalance, enforceMinRequiredBalance, minBalanceForInterestCalculation, onHoldFunds,
                 nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroup, 
-                lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat);
+                lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat, savingsAmountOnHold);
     }
 
     public static SavingsAccountData lookup(final Long accountId, final String accountNo, final EnumOptionData depositType) {
@@ -204,6 +205,7 @@
         final Integer daysToInactive = null;
         final Integer daysToDormancy = null;
         final Integer daysToEscheat = null;
+        final BigDecimal savingsAmountOnHold = null;
 
         return new SavingsAccountData(accountId, accountNo, depositType, externalId, groupId, groupName, clientId, clientName, productId,
                 productName, fieldOfficerId, fieldOfficerName, status, subStatus, timeline, currency, nominalAnnualInterestRate,
@@ -214,7 +216,7 @@
                 lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges, chargeOptions, allowOverdraft, overdraftLimit,
                 minRequiredBalance, enforceMinRequiredBalance, minBalanceForInterestCalculation, onHoldFunds,
                 nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroup, 
-                lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat);
+                lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat, savingsAmountOnHold);
     }
 
     public static SavingsAccountData lookupWithProductDetails(final Long accountId, final String accountNo,
@@ -274,6 +276,7 @@
         final Integer daysToInactive = null;
         final Integer daysToDormancy = null;
         final Integer daysToEscheat = null;
+		final BigDecimal savingsAmountOnHold = null;
 
         return new SavingsAccountData(accountId, accountNo, depositType, externalId, groupId, groupName, clientId, clientName, productId,
                 productName, fieldOfficerId, fieldOfficerName, status, subStatus, timeline, currency, nominalAnnualInterestRate,
@@ -284,7 +287,7 @@
                 lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges, chargeOptions, allowOverdraft, overdraftLimit,
                 minRequiredBalance, enforceMinRequiredBalance, minBalanceForInterestCalculation, onHoldFunds,
                 nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroup, 
-                lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat);
+                lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat, savingsAmountOnHold);
     }
 
     public static SavingsAccountData withTemplateOptions(final SavingsAccountData account, final SavingsAccountData template,
@@ -320,7 +323,7 @@
                 account.minBalanceForInterestCalculation, account.onHoldFunds, account.nominalAnnualInterestRateOverdraft,
                 account.minOverdraftForInterestCalculation, account.withHoldTax, account.taxGroup, 
                 account.lastActiveTransactionDate, account.isDormancyTrackingActive, account.daysToInactive, 
-                account.daysToDormancy, account.daysToEscheat);
+                account.daysToDormancy, account.daysToEscheat, account.savingsAmountOnHold);
     }
 
     public static SavingsAccountData withTemplateOptions(final SavingsAccountData account,
@@ -345,7 +348,7 @@
                 account.overdraftLimit, account.minRequiredBalance, account.enforceMinRequiredBalance,
                 account.minBalanceForInterestCalculation, account.onHoldFunds, account.nominalAnnualInterestRateOverdraft,
                 account.minOverdraftForInterestCalculation, account.withHoldTax, account.taxGroup, account.lastActiveTransactionDate, 
-                account.isDormancyTrackingActive, account.daysToInactive, account.daysToDormancy, account.daysToEscheat);
+                account.isDormancyTrackingActive, account.daysToInactive, account.daysToDormancy, account.daysToEscheat, account.savingsAmountOnHold);
     }
 
     public static SavingsAccountData withClientTemplate(final Long clientId, final String clientName, final Long groupId,
@@ -407,6 +410,7 @@
         final Integer daysToInactive = null;
         final Integer daysToDormancy = null;
         final Integer daysToEscheat = null;
+		final BigDecimal savingsAmountOnHold = null;
 
         return new SavingsAccountData(id, accountNo, depositType, externalId, groupId, groupName, clientId, clientName, productId,
                 productName, fieldOfficerId, fieldOfficerName, status, subStatus, timeline, currency, nominalAnnualInterestRate,
@@ -417,7 +421,7 @@
                 lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges, chargeOptions, allowOverdraft, overdraftLimit,
                 minRequiredBalance, enforceMinRequiredBalance, minBalanceForInterestCalculation, onHoldFunds,
                 nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroup, 
-                lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat);
+                lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat, savingsAmountOnHold);
     }
 
     private SavingsAccountData(final Long id, final String accountNo, final EnumOptionData depositType, final String externalId,
@@ -441,7 +445,7 @@
             final BigDecimal onHoldFunds, final BigDecimal nominalAnnualInterestRateOverdraft,
             final BigDecimal minOverdraftForInterestCalculation, final boolean withHoldTax, final TaxGroupData taxGroup, 
             final LocalDate lastActiveTransactionDate, final boolean isDormancyTrackingActive, final Integer daysToInactive, 
-            final Integer daysToDormancy, final Integer daysToEscheat) {
+            final Integer daysToDormancy, final Integer daysToEscheat, final BigDecimal savingsAmountOnHold) {
         this.id = id;
         this.accountNo = accountNo;
         this.depositType = depositType;
@@ -507,6 +511,7 @@
         this.daysToInactive = daysToInactive;
         this.daysToDormancy = daysToDormancy;
         this.daysToEscheat = daysToEscheat;
+        this.savingsAmountOnHold = savingsAmountOnHold;
     }
 
     private SavingsAccountChargeData getWithdrawalFee() {
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionDataValidator.java
index fcd7a88..b386858 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionDataValidator.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionDataValidator.java
@@ -29,7 +29,6 @@
 import static org.apache.fineract.portfolio.savings.SavingsApiConstants.transactionAmountParamName;
 import static org.apache.fineract.portfolio.savings.SavingsApiConstants.transactionDateParamName;
 import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withdrawBalanceParamName;
-import static org.apache.fineract.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_HOLD_AMOUNT_REQUEST_DATA_PARAMETERS;
 import static org.apache.fineract.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME;
 
 import java.lang.reflect.Type;
@@ -67,6 +66,9 @@
 public class SavingsAccountTransactionDataValidator {
 
     private final FromJsonHelper fromApiJsonHelper;
+	private static final Set<String> SAVINGS_ACCOUNT_HOLD_AMOUNT_REQUEST_DATA_PARAMETERS = new HashSet<>(
+			Arrays.asList(transactionDateParamName, SavingsApiConstants.dateFormatParamName,
+					SavingsApiConstants.localeParamName, transactionAmountParamName));
 
     @Autowired
     public SavingsAccountTransactionDataValidator(final FromJsonHelper fromApiJsonHelper) {
@@ -121,7 +123,7 @@
         throwExceptionIfValidationWarningsExist(dataValidationErrors);
     }
 
-    public void validateClosing(final JsonCommand command) {
+    public void validateClosing(final JsonCommand command, final SavingsAccount account) {
         final String json = command.json();
 
         if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
@@ -144,6 +146,11 @@
             baseDataValidator.reset().parameter(withdrawBalanceParamName).value(withdrawBalance).isOneOfTheseValues(true, false);
         }
 
+		if (account.getSavingsHoldAmount().compareTo(BigDecimal.ZERO) == 1) {
+			baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
+					"amount.is.on.hold.release.the.amount.to.continue", account.getId());
+		}
+                      
         validatePaymentTypeDetails(baseDataValidator, element);
 
         throwExceptionIfValidationWarningsExist(dataValidationErrors);
@@ -192,10 +199,9 @@
             baseDataValidator.reset().parameter(SavingsApiConstants.statusParamName)
                     .failWithCodeNoParameterAddedToErrorCode(SavingsApiConstants.ERROR_MSG_SAVINGS_ACCOUNT_NOT_ACTIVE);
         }
-        account.holdFunds(amount);
-        if (account.getWithdrawableBalance().compareTo(BigDecimal.ZERO) == -1) {
+        account.holdAmount(amount);
+        if (account.getWithdrawableBalance().compareTo(BigDecimal.ZERO)==-1){
             baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("insufficient balance", account.getId());
-            baseDataValidator.failWithCode("validation.msg.savingsaccount.insufficient balance", "Insufficient balance");
         }
         LocalDate lastTransactionDate = account.retrieveLastTransactionDate();
         // compare two dates now
@@ -206,7 +212,8 @@
 
         throwExceptionIfValidationWarningsExist(dataValidationErrors);
         final PaymentDetail paymentDetails = null;
-        Date createdDate = DateUtils.getDateOfTenant();
+        Date createdDate = new Date();
+        
         SavingsAccountTransaction transaction = SavingsAccountTransaction.holdAmount(account, account.office(), paymentDetails,
                 transactionDate, Money.of(account.getCurrency(), amount), createdDate, createdUser);
         return transaction;
@@ -217,15 +224,24 @@
         final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
                 .resource(SAVINGS_ACCOUNT_RESOURCE_NAME);
 
-        if (holdTransaction == null) {
-            baseDataValidator.failWithCode("validation.msg.validation.errors.exist", "Transaction not found");
-        } else if (holdTransaction.getReleaseIdOfHoldAmountTransaction() != null) {
-            baseDataValidator.parameter(SavingsApiConstants.amountParamName).value(holdTransaction.getAmount()).failWithCode("validation.msg.amount.is.not.on.hold",
-                    "Transaction amount is not on hold");
-        }
+		if (holdTransaction == null) {
+			baseDataValidator.failWithCode("validation.msg.validation.errors.exist", "Transaction not found");
+		} else if (holdTransaction.getReleaseIdOfHoldAmountTransaction() != null) {
+			baseDataValidator.parameter(SavingsApiConstants.amountParamName).value(holdTransaction.getAmount())
+					.failWithCode("validation.msg.amount.is.not.on.hold", "Transaction amount is not on hold");
+		}
+
+		if (holdTransaction != null) {
+			boolean isActive = holdTransaction.getSavingsAccount().isActive();
+			if (!isActive) {
+				baseDataValidator.reset().parameter(SavingsApiConstants.statusParamName)
+						.failWithCodeNoParameterAddedToErrorCode(
+								SavingsApiConstants.ERROR_MSG_SAVINGS_ACCOUNT_NOT_ACTIVE);
+			}
+		}
 
         throwExceptionIfValidationWarningsExist(dataValidationErrors);
-        Date createdDate = DateUtils.getDateOfTenant();
+        Date createdDate = new Date();
         LocalDate transactionDate = DateUtils.getLocalDateOfTenant();
         SavingsAccountTransaction transaction = SavingsAccountTransaction.releaseAmount(holdTransaction, transactionDate, createdDate,
                 createdUser);
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java
index f904b09..d7a6298 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java
@@ -325,6 +325,8 @@
     @JoinColumn(name = "tax_group_id")
     private TaxGroup taxGroup;
 
+    @Column(name = "total_savings_amount_on_hold", scale = 6, precision = 19, nullable = true)
+    private BigDecimal savingsOnHoldAmount;
     protected SavingsAccount() {
         //
     }
@@ -1051,19 +1053,16 @@
         Money runningBalance = Money.zero(this.currency);
         Money minRequiredBalance = minRequiredBalanceDerived(getCurrency());
         LocalDate lastSavingsDate = null;
+        final BigDecimal withdrawalFee = null;
         for (final SavingsAccountTransaction transaction : transactionsSortedByDate) {
             if (transaction.isNotReversed() && transaction.isCredit()) {
                 runningBalance = runningBalance.plus(transaction.getAmount(this.currency));
             } else if (transaction.isNotReversed() && transaction.isDebit()) {
                 runningBalance = runningBalance.minus(transaction.getAmount(this.currency));
-            } else if(transaction.isAmountOnHold() && transaction.getReleaseIdOfHoldAmountTransaction() == null){
-                runningBalance = runningBalance.minus(transaction.getAmount(this.currency));
             }else {
                 continue;
             }
 
-            final BigDecimal withdrawalFee = null;
-
             /*
              * Loop through the onHold funds and see if we need to deduct or add
              * to minimum required balance and the point in time the transaction
@@ -1093,6 +1092,12 @@
             lastSavingsDate = transaction.transactionLocalDate();
 
         }
+		if (this.getSavingsHoldAmount().compareTo(BigDecimal.ZERO) == 1) {
+			if (runningBalance.minus(this.getSavingsHoldAmount()).isLessThanZero()) {
+				throw new InsufficientAccountBalanceException("transactionAmount", getAccountBalance(), withdrawalFee,
+						transactionAmount);
+			}
+		}
     }
 
     public void validateAccountBalanceDoesNotBecomeNegative(final String transactionAction,
@@ -2753,7 +2758,7 @@
     }
 
     public BigDecimal getWithdrawableBalance() {
-        return getAccountBalance().subtract(minRequiredBalanceDerived(getCurrency()).getAmount()).subtract(this.getOnHoldFunds());
+        return getAccountBalance().subtract(minRequiredBalanceDerived(getCurrency()).getAmount()).subtract(this.getOnHoldFunds()).subtract(this.getSavingsHoldAmount());
     }
 
     public TaxGroup getTaxGroup() {
@@ -2891,14 +2896,17 @@
             baseDataValidator.reset().parameter(SavingsApiConstants.statusParamName)
                     .failWithCodeNoParameterAddedToErrorCode(SavingsApiConstants.ERROR_MSG_SAVINGS_ACCOUNT_NOT_ACTIVE);
         }
-        if (SavingsAccountSubStatusEnum.BLOCK.hasStateOf(SavingsAccountSubStatusEnum.fromInt(currentSubstatus))
-                || SavingsAccountSubStatusEnum.BLOCK_DEBIT.hasStateOf(SavingsAccountSubStatusEnum.fromInt(currentSubstatus))) {
+        if (SavingsAccountSubStatusEnum.BLOCK.hasStateOf(SavingsAccountSubStatusEnum.fromInt(currentSubstatus))) {
 
             baseDataValidator.reset().parameter(SavingsApiConstants.subStatusParamName).value(SavingsAccountSubStatusEnum.fromInt(currentSubstatus))
                     .failWithCodeNoParameterAddedToErrorCode("currently.set");
         }
         if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
-        this.sub_status = SavingsAccountSubStatusEnum.BLOCK_CREDIT.getValue();
+		if (SavingsAccountSubStatusEnum.BLOCK_DEBIT.hasStateOf(SavingsAccountSubStatusEnum.fromInt(currentSubstatus))) {
+			this.sub_status = SavingsAccountSubStatusEnum.BLOCK.getValue();
+		} else {
+			this.sub_status = SavingsAccountSubStatusEnum.BLOCK_CREDIT.getValue();
+		}
         actualChanges.put(SavingsApiConstants.subStatusParamName, SavingsEnumerations.subStatus(this.sub_status));
 
         return actualChanges;
@@ -2918,12 +2926,17 @@
         }
 
         final SavingsAccountSubStatusEnum currentSubStatus = SavingsAccountSubStatusEnum.fromInt(this.sub_status);
-        if (!SavingsAccountSubStatusEnum.BLOCK_CREDIT.hasStateOf(currentSubStatus)) {
-            baseDataValidator.reset().parameter(SavingsApiConstants.statusParamName)
-                    .failWithCodeNoParameterAddedToErrorCode("credits.are.not.blocked");
-        }
+		if (!(SavingsAccountSubStatusEnum.BLOCK_CREDIT.hasStateOf(currentSubStatus)
+				|| SavingsAccountSubStatusEnum.BLOCK.hasStateOf(currentSubStatus))) {
+			baseDataValidator.reset().parameter(SavingsApiConstants.statusParamName)
+					.failWithCodeNoParameterAddedToErrorCode("credits.are.not.blocked");
+		}
         if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
-        this.sub_status = SavingsAccountSubStatusEnum.NONE.getValue();
+		if (SavingsAccountSubStatusEnum.BLOCK.hasStateOf(currentSubStatus)) {
+			this.sub_status = SavingsAccountSubStatusEnum.BLOCK_DEBIT.getValue();
+		} else {
+			this.sub_status = SavingsAccountSubStatusEnum.NONE.getValue();
+		}
         actualChanges.put(SavingsApiConstants.subStatusParamName, SavingsEnumerations.subStatus(this.sub_status));
         return actualChanges;
     }
@@ -2942,15 +2955,17 @@
                     .failWithCodeNoParameterAddedToErrorCode(SavingsApiConstants.ERROR_MSG_SAVINGS_ACCOUNT_NOT_ACTIVE);
 
         }
-        if (SavingsAccountSubStatusEnum.BLOCK.hasStateOf(SavingsAccountSubStatusEnum.fromInt(currentSubstatus))
-                || SavingsAccountSubStatusEnum.BLOCK_CREDIT.hasStateOf(SavingsAccountSubStatusEnum.fromInt(currentSubstatus))) {
+        if (SavingsAccountSubStatusEnum.BLOCK.hasStateOf(SavingsAccountSubStatusEnum.fromInt(currentSubstatus))) {
 
             baseDataValidator.reset().parameter(SavingsApiConstants.subStatusParamName).value(SavingsAccountSubStatusEnum.fromInt(currentSubstatus))
                     .failWithCodeNoParameterAddedToErrorCode("currently.set");
         }
         if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
-
-        this.sub_status = SavingsAccountSubStatusEnum.BLOCK_DEBIT.getValue();
+		if (SavingsAccountSubStatusEnum.BLOCK_CREDIT.hasStateOf(SavingsAccountSubStatusEnum.fromInt(currentSubstatus))) {
+			this.sub_status = SavingsAccountSubStatusEnum.BLOCK.getValue();
+		} else {
+			this.sub_status = SavingsAccountSubStatusEnum.BLOCK_DEBIT.getValue();
+		}
         actualChanges.put(SavingsApiConstants.subStatusParamName, SavingsEnumerations.subStatus(this.sub_status));
 
         return actualChanges;
@@ -2973,12 +2988,17 @@
         }
 
         final SavingsAccountSubStatusEnum currentSubStatus = SavingsAccountSubStatusEnum.fromInt(this.sub_status);
-        if (!SavingsAccountSubStatusEnum.BLOCK_DEBIT.hasStateOf(currentSubStatus)) {
-            baseDataValidator.reset().parameter(SavingsApiConstants.subStatusParamName)
-                    .failWithCodeNoParameterAddedToErrorCode("debits.are.not.blocked");
-        }
+		if (!(SavingsAccountSubStatusEnum.BLOCK_DEBIT.hasStateOf(currentSubStatus)
+				|| SavingsAccountSubStatusEnum.BLOCK.hasStateOf(currentSubStatus))) {
+			baseDataValidator.reset().parameter(SavingsApiConstants.subStatusParamName)
+					.failWithCodeNoParameterAddedToErrorCode("debits.are.not.blocked");
+		}
         if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
-        this.sub_status = SavingsAccountSubStatusEnum.NONE.getValue();
+		if (SavingsAccountSubStatusEnum.BLOCK.hasStateOf(currentSubStatus)) {
+			this.sub_status = SavingsAccountSubStatusEnum.BLOCK_CREDIT.getValue();
+		} else {
+			this.sub_status = SavingsAccountSubStatusEnum.NONE.getValue();
+		}
         actualChanges.put(SavingsApiConstants.subStatusParamName, SavingsEnumerations.subStatus(this.sub_status));
         return actualChanges;
     }
@@ -3006,9 +3026,27 @@
 
     public LocalDate retrieveLastTransactionDate() {
         final List<SavingsAccountTransaction> transactionsSortedByDate = retreiveListOfTransactions();
-        SavingsAccountTransaction lastTransaction = transactionsSortedByDate.get(transactionsSortedByDate.size() - 1);
-        return lastTransaction.transactionLocalDate();
+        SavingsAccountTransaction lastTransaction = null;
+        if (transactionsSortedByDate.size() > 0) {
+            lastTransaction = transactionsSortedByDate.get(transactionsSortedByDate.size() - 1);
+        }
+        LocalDate lastransactionDate = null;
+        if (lastTransaction != null) {
+            lastransactionDate = lastTransaction.transactionLocalDate();
+        }
+        return lastransactionDate;
     }
-    
-    
+
+    public BigDecimal getSavingsHoldAmount() {
+        return this.savingsOnHoldAmount == null ? BigDecimal.ZERO : this.savingsOnHoldAmount;
+    }
+
+    public void holdAmount(BigDecimal amount) {
+        this.savingsOnHoldAmount = getSavingsHoldAmount().add(amount);
+    }
+
+    public void releaseAmount(BigDecimal amount) {
+        this.savingsOnHoldAmount = getSavingsHoldAmount().subtract(amount);
+    }
+
 }
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java
index 12de527..cf5ae03 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java
@@ -787,4 +787,8 @@
     public Long getReleaseIdOfHoldAmountTransaction() {
         return this.releaseIdOfHoldAmountTransaction;
     }
+    
+	public boolean isAmountOnHoldNotReleased() {
+		return (isAmountOnHold() && getReleaseIdOfHoldAmountTransaction() == null);
+	}	    
 }
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java
index 011256a..b38ca37 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java
@@ -22,8 +22,11 @@
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Set;
 
 import org.apache.fineract.infrastructure.core.data.EnumOptionData;
 import org.apache.fineract.infrastructure.core.data.PaginationParameters;
@@ -122,6 +125,9 @@
     private final CalendarReadPlatformService calendarReadPlatformService;
     private final DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd");
     private final PaymentTypeReadPlatformService paymentTypeReadPlatformService;
+    // allowed column names for sorting the query result
+    private final static Set<String> supportedOrderByValues = new HashSet<>(Arrays.asList("id", "accountNumbr",
+            "officeId", "officeName"));
 
     @Autowired
     public DepositAccountReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource,
@@ -178,7 +184,7 @@
     public Page<DepositAccountData> retrieveAllPaged(final DepositAccountType depositAccountType,
             final PaginationParameters paginationParameters) {
 
-        this.paginationParametersDataValidator.validateParameterValues(paginationParameters, DepositsApiConstants.supportedOrderByValues,
+        this.paginationParametersDataValidator.validateParameterValues(paginationParameters, supportedOrderByValues,
                 depositAccountType.resourceName());
 
         final DepositAccountMapper depositAccountMapper = this.getDepositAccountMapper(depositAccountType);
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java
index 1b68e3f..a9704eb 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java
@@ -304,6 +304,7 @@
             sqlBuilder.append("sa.withhold_tax as withHoldTax, ");
             sqlBuilder.append("sa.total_withhold_tax_derived as totalWithholdTax, ");
             sqlBuilder.append("sa.last_interest_calculation_date as lastInterestCalculationDate, ");
+            sqlBuilder.append("sa.total_savings_amount_on_hold as onHoldAmount, ");
             sqlBuilder.append("tg.id as taxGroupId, tg.name as taxGroupName, ");
             sqlBuilder.append("(select IFNULL(max(sat.transaction_date),sa.activatedon_date) ");
             sqlBuilder.append("from m_savings_account_transaction as sat ");
@@ -511,12 +512,19 @@
                     "minBalanceForInterestCalculation");
             final BigDecimal onHoldFunds = rs.getBigDecimal("onHoldFunds");
             
+            final BigDecimal onHoldAmount = rs.getBigDecimal("onHoldAmount");
+            
             BigDecimal availableBalance = accountBalance;
             if (availableBalance != null && onHoldFunds != null) {
 
                 availableBalance = availableBalance.subtract(onHoldFunds);
             }
             
+            if (availableBalance != null && onHoldAmount != null) {
+
+                availableBalance = availableBalance.subtract(onHoldAmount);
+            }
+            
             BigDecimal interestNotPosted = BigDecimal.ZERO;
             LocalDate lastInterestCalculationDate = null;
             if(totalInterestEarned != null){
@@ -542,7 +550,7 @@
                     interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers,
                     summary, allowOverdraft, overdraftLimit, minRequiredBalance, enforceMinRequiredBalance,
                     minBalanceForInterestCalculation, onHoldFunds, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, 
-                    taxGroupData, lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat);
+                    taxGroupData, lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat, onHoldAmount);
         }
     }
 
@@ -1074,7 +1082,8 @@
             // final LocalDate annualFeeNextDueDate = null;
             final SavingsAccountSummaryData summary = null;
             final BigDecimal onHoldFunds = null;
-            
+            final BigDecimal savingsAmountOnHold  = null;
+
             final SavingsAccountSubStatusEnumData subStatus = null;
             final LocalDate lastActiveTransactionDate = null;
             final boolean isDormancyTrackingActive = false;
@@ -1090,7 +1099,7 @@
                     interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers,
                     summary, allowOverdraft, overdraftLimit, minRequiredBalance, enforceMinRequiredBalance,
                     minBalanceForInterestCalculation, onHoldFunds, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, 
-                    taxGroupData, lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat);
+                    taxGroupData, lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat, savingsAmountOnHold);
         }
     }
 
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java
index f9e52b2..03409ed 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java
@@ -612,8 +612,9 @@
     public CommandProcessingResult close(final Long savingsId, final JsonCommand command) {
         final AppUser user = this.context.authenticatedUser();
         
-        this.savingsAccountTransactionDataValidator.validateClosing(command);
         final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
+        this.savingsAccountTransactionDataValidator.validateClosing(command, account);
+        
         final boolean isLinkedWithAnyActiveLoan = this.accountAssociationsReadPlatformService.isLinkedWithAnyActiveAccount(savingsId);
 
         if (isLinkedWithAnyActiveLoan) {
@@ -1403,7 +1404,7 @@
                 .validateReleaseAmountAndAssembleForm(holdTransaction, submittedBy);
         final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
         checkClientOrGroupActive(account);
-        account.releaseFunds(transaction.getAmount());
+        account.releaseAmount(transaction.getAmount());
 
         this.savingsAccountTransactionRepository.save(transaction);
         holdTransaction.updateReleaseId(transaction.getId());
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/SelfServiceApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/SelfServiceApiConstants.java
new file mode 100644
index 0000000..2362523
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/SelfServiceApiConstants.java
@@ -0,0 +1,48 @@
+/**
+ * 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.registration;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class SelfServiceApiConstants {
+
+    public static final String accountNumberParamName = "accountNumber";
+    public static final String passwordParamName = "password";
+    public static final String firstNameParamName = "firstName";
+    public static final String mobileNumberParamName = "mobileNumber";
+    public static final String lastNameParamName = "lastName";
+    public static final String emailParamName = "email";
+    public static final String usernameParamName = "username";
+    public static final String authenticationTokenParamName = "authenticationToken";
+    public static final String authenticationModeParamName = "authenticationMode";
+    public static final String emailModeParamName = "email";
+    public static final String mobileModeParamName = "mobile";
+    public static final String requestIdParamName = "requestId";
+    public static final String createRequestSuccessMessage = "Self service request created.";
+    public static final Set<String> REGISTRATION_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(usernameParamName,
+            accountNumberParamName, passwordParamName, firstNameParamName, mobileNumberParamName, lastNameParamName, emailParamName,
+            authenticationModeParamName));
+    public static final Set<String> CREATE_USER_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(requestIdParamName,
+            authenticationTokenParamName));
+    public static final Object[] SUPPORTED_AUTHENTICATION_MODE_PARAMETERS = new Object[] {emailModeParamName,
+            mobileModeParamName};
+    
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/api/SelfServiceRegistrationApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/api/SelfServiceRegistrationApiResource.java
new file mode 100644
index 0000000..64dcfa9
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/api/SelfServiceRegistrationApiResource.java
@@ -0,0 +1,68 @@
+/**
+ * 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.registration.api;
+
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
+import org.apache.fineract.portfolio.note.data.NoteData;
+import org.apache.fineract.portfolio.self.registration.SelfServiceApiConstants;
+import org.apache.fineract.portfolio.self.registration.service.SelfServiceRegistrationWritePlatformService;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+@Path("/self/registration")
+@Component
+@Scope("singleton")
+public class SelfServiceRegistrationApiResource {
+
+    private final SelfServiceRegistrationWritePlatformService selfServiceRegistrationWritePlatformService;
+    private final DefaultToApiJsonSerializer<AppUser> toApiJsonSerializer;
+
+    @Autowired
+    public SelfServiceRegistrationApiResource(
+            final SelfServiceRegistrationWritePlatformService selfServiceRegistrationWritePlatformService,
+            final DefaultToApiJsonSerializer<AppUser> toApiJsonSerializer) {
+        this.selfServiceRegistrationWritePlatformService = selfServiceRegistrationWritePlatformService;
+        this.toApiJsonSerializer = toApiJsonSerializer;
+    }
+
+    @POST
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String createSelfServiceRegistrationRequest(final String apiRequestBodyAsJson) {
+        this.selfServiceRegistrationWritePlatformService.createRegistrationRequest(apiRequestBodyAsJson);
+        return SelfServiceApiConstants.createRequestSuccessMessage;
+    }
+
+    @POST
+    @Path("user")
+    @Produces({ MediaType.APPLICATION_JSON })
+    public String createSelfServiceUser(final String apiRequestBodyAsJson) {
+        AppUser user = this.selfServiceRegistrationWritePlatformService.createUser(apiRequestBodyAsJson);
+        return this.toApiJsonSerializer.serialize(CommandProcessingResult.resourceResult(user.getId(), null));
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/data/SelfServiceRegistrationData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/data/SelfServiceRegistrationData.java
new file mode 100644
index 0000000..9f62500
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/data/SelfServiceRegistrationData.java
@@ -0,0 +1,70 @@
+/**
+ * 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.registration.data;
+
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.joda.time.LocalDate;
+
+public class SelfServiceRegistrationData {
+
+    @SuppressWarnings("unused")
+    private final Long id;
+    @SuppressWarnings("unused")
+    private Client client;
+    @SuppressWarnings("unused")
+    private String firstName;
+    @SuppressWarnings("unused")
+    private String lastName;
+    @SuppressWarnings("unused")
+    private String mobileNumber;
+    @SuppressWarnings("unused")
+    private String email;
+    @SuppressWarnings("unused")
+    private String authenticationToken;
+    @SuppressWarnings("unused")
+    private String username;
+    @SuppressWarnings("unused")
+    private String password;
+    @SuppressWarnings("unused")
+    private LocalDate createdDate;
+
+    public SelfServiceRegistrationData(final Long id, final Client client, final String firstName, final String lastName,
+            final String mobileNumber, final String email, final String authenticationToken, final String username, final String password,
+            final LocalDate createdDate) {
+        this.id = id;
+        this.client = client;
+        this.firstName = firstName;
+        this.lastName = lastName;
+        this.mobileNumber = mobileNumber;
+        this.email = email;
+        this.authenticationToken = authenticationToken;
+        this.username = username;
+        this.password = password;
+        this.createdDate = createdDate;
+    }
+
+    public static SelfServiceRegistrationData getData(final Long id, final Client client, final String firstName, final String lastName,
+            final String mobileNumber, final String email, final String authenticationToken, final String username, final String password,
+            final LocalDate createdDate) {
+        return new SelfServiceRegistrationData(id, client, firstName, lastName, mobileNumber, email, authenticationToken, username,
+                password, createdDate);
+
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/domain/SelfServiceRegistration.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/domain/SelfServiceRegistration.java
new file mode 100644
index 0000000..7c226c5
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/domain/SelfServiceRegistration.java
@@ -0,0 +1,131 @@
+/**
+ * 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.registration.domain;
+
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
+import org.apache.fineract.portfolio.client.domain.Client;
+
+@Entity
+@Table(name = "request_audit_table")
+public class SelfServiceRegistration extends AbstractPersistableCustom<Long> {
+
+    @ManyToOne
+    @JoinColumn(name = "client_id", nullable = false)
+    private Client client;
+
+    @Column(name = "account_number", length = 100, nullable = false)
+    private String accountNumber;
+
+    @Column(name = "firstname", length = 100, nullable = false)
+    private String firstName;
+
+    @Column(name = "lastname", length = 100, nullable = false)
+    private String lastName;
+
+    @Column(name = "mobile_number", length = 50, nullable = true)
+    private String mobileNumber;
+
+    @Column(name = "email", length = 100, nullable = false)
+    private String email;
+
+    @Column(name = "authentication_token", length = 100, nullable = true)
+    private String authenticationToken;
+
+    @Column(name = "username", length = 100, nullable = false)
+    private String username;
+
+    @Column(name = "password", length = 100, nullable = false)
+    private String password;
+
+    @Column(name = "created_date", nullable = false)
+    @Temporal(TemporalType.DATE)
+    private Date createdDate;
+
+    public SelfServiceRegistration(final Client client, String accountNumber, final String firstName, final String lastName,
+            final String mobileNumber, final String email, final String authenticationToken, final String username, final String password) {
+        this.client = client;
+        this.accountNumber = accountNumber;
+        this.firstName = firstName;
+        this.lastName = lastName;
+        this.mobileNumber = mobileNumber;
+        this.email = email;
+        this.authenticationToken = authenticationToken;
+        this.username = username;
+        this.password = password;
+        this.createdDate = new Date();
+    }
+
+    public static SelfServiceRegistration instance(final Client client, final String accountNumber, final String firstname,
+            final String lastName, final String mobileNumber, final String email, final String authenticationToken, final String username,
+            final String password) {
+        return new SelfServiceRegistration(client, accountNumber, firstname, lastName, mobileNumber, email, authenticationToken, username,
+                password);
+    }
+
+    public Client getClient() {
+        return this.client;
+    }
+
+    public String getFirstName() {
+        return this.firstName;
+    }
+
+    public String getLastName() {
+        return this.lastName;
+    }
+
+    public String getMobileNumber() {
+        return this.mobileNumber;
+    }
+
+    public String getEmail() {
+        return this.email;
+    }
+
+    public String getAuthenticationToken() {
+        return this.authenticationToken;
+    }
+
+    public Date getCreatedDate() {
+        return this.createdDate;
+    }
+
+    public String getUsername() {
+        return this.username;
+    }
+
+    public String getPassword() {
+        return this.password;
+    }
+
+    public String getAccountNumber() {
+        return this.accountNumber;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/domain/SelfServiceRegistrationRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/domain/SelfServiceRegistrationRepository.java
new file mode 100644
index 0000000..2f93482
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/domain/SelfServiceRegistrationRepository.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.registration.domain;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+
+public interface SelfServiceRegistrationRepository extends JpaRepository<SelfServiceRegistration, Long>,
+        JpaSpecificationExecutor<SelfServiceRegistration> {
+
+    public static final String FIND_BY_REQUEST_AND_AUTHENTICATION_TOKEN = "select request from SelfServiceRegistration request where request.id = :id and "
+            + "request.authenticationToken = :authenticationToken";
+
+    @Query(FIND_BY_REQUEST_AND_AUTHENTICATION_TOKEN)
+    SelfServiceRegistration getRequestByIdAndAuthenticationToken(@Param("id") Long id,
+            @Param("authenticationToken") String authenticationToken);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/exception/SelfServiceRegistrationNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/exception/SelfServiceRegistrationNotFoundException.java
new file mode 100644
index 0000000..888a827
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/exception/SelfServiceRegistrationNotFoundException.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.registration.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+public class SelfServiceRegistrationNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+    public SelfServiceRegistrationNotFoundException(Long requestId, String authenticationToken) {
+        super("error.msg.self.service.registration.not.found", "Self service registration not found with request id : " + requestId
+                + " and authentication token :" + authenticationToken);
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/service/SelfServiceRegistrationReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/service/SelfServiceRegistrationReadPlatformService.java
new file mode 100644
index 0000000..9ff45b4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/service/SelfServiceRegistrationReadPlatformService.java
@@ -0,0 +1,26 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.self.registration.service;
+
+public interface SelfServiceRegistrationReadPlatformService {
+
+    public boolean isClientExist(String accountNumber, String firstName, String lastName, String mobileNumber,
+            boolean isEmailAuthenticationMode);
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/service/SelfServiceRegistrationReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/service/SelfServiceRegistrationReadPlatformServiceImpl.java
new file mode 100644
index 0000000..ce265e6
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/service/SelfServiceRegistrationReadPlatformServiceImpl.java
@@ -0,0 +1,50 @@
+/**
+ * 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.registration.service;
+
+import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Service;
+
+@Service
+public class SelfServiceRegistrationReadPlatformServiceImpl implements SelfServiceRegistrationReadPlatformService {
+
+    private final JdbcTemplate jdbcTemplate;
+
+    @Autowired
+    public SelfServiceRegistrationReadPlatformServiceImpl(final RoutingDataSource dataSource) {
+        this.jdbcTemplate = new JdbcTemplate(dataSource);
+    }
+
+    @Override
+    public boolean isClientExist(String accountNumber, String firstName, String lastName, String mobileNumber,
+            boolean isEmailAuthenticationMode) {
+        String sql = "select count(*) from m_client where account_no = ? and firstname = ? and lastname = ?";
+        Object[] params = new Object[] { accountNumber, firstName, lastName };
+        if (!isEmailAuthenticationMode) {
+            sql = sql + " and mobile_no = ?";
+            params = new Object[] { accountNumber, firstName, lastName, mobileNumber };
+        }
+        Integer count = this.jdbcTemplate.queryForObject(sql, params, Integer.class);
+        if (count == 0) { return false; }
+        return true;
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/service/SelfServiceRegistrationWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/service/SelfServiceRegistrationWritePlatformService.java
new file mode 100644
index 0000000..7827c44
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/service/SelfServiceRegistrationWritePlatformService.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.registration.service;
+
+import org.apache.fineract.portfolio.self.registration.domain.SelfServiceRegistration;
+import org.apache.fineract.useradministration.domain.AppUser;
+
+public interface SelfServiceRegistrationWritePlatformService {
+
+    public SelfServiceRegistration createRegistrationRequest(String apiRequestBodyAsJson);
+
+    public AppUser createUser(String apiRequestBodyAsJson);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/service/SelfServiceRegistrationWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/service/SelfServiceRegistrationWritePlatformServiceImpl.java
new file mode 100644
index 0000000..7797b2f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/registration/service/SelfServiceRegistrationWritePlatformServiceImpl.java
@@ -0,0 +1,293 @@
+/**
+ * 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.registration.service;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.persistence.PersistenceException;
+
+import org.apache.commons.lang.exception.ExceptionUtils;
+import org.apache.fineract.infrastructure.campaigns.sms.data.SmsProviderData;
+import org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaign;
+import org.apache.fineract.infrastructure.campaigns.sms.service.SmsCampaignDropdownReadPlatformService;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.ApiParameterError;
+import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
+import org.apache.fineract.infrastructure.core.domain.EmailDetail;
+import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
+import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.core.service.GmailBackedPlatformEmailService;
+import org.apache.fineract.infrastructure.sms.domain.SmsMessage;
+import org.apache.fineract.infrastructure.sms.domain.SmsMessageRepository;
+import org.apache.fineract.infrastructure.sms.domain.SmsMessageStatusType;
+import org.apache.fineract.infrastructure.sms.scheduler.SmsMessageScheduledJobService;
+import org.apache.fineract.organisation.staff.domain.Staff;
+import org.apache.fineract.portfolio.client.domain.Client;
+import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
+import org.apache.fineract.portfolio.client.exception.ClientNotFoundException;
+import org.apache.fineract.portfolio.group.domain.Group;
+import org.apache.fineract.portfolio.self.registration.SelfServiceApiConstants;
+import org.apache.fineract.portfolio.self.registration.domain.SelfServiceRegistration;
+import org.apache.fineract.portfolio.self.registration.domain.SelfServiceRegistrationRepository;
+import org.apache.fineract.portfolio.self.registration.exception.SelfServiceRegistrationNotFoundException;
+import org.apache.fineract.useradministration.domain.AppUser;
+import org.apache.fineract.useradministration.domain.PasswordValidationPolicy;
+import org.apache.fineract.useradministration.domain.PasswordValidationPolicyRepository;
+import org.apache.fineract.useradministration.domain.Role;
+import org.apache.fineract.useradministration.domain.UserDomainService;
+import org.apache.fineract.useradministration.service.AppUserReadPlatformService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.security.authentication.AuthenticationServiceException;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.stereotype.Service;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+
+@Service
+public class SelfServiceRegistrationWritePlatformServiceImpl implements SelfServiceRegistrationWritePlatformService {
+
+    private final SelfServiceRegistrationRepository selfServiceRegistrationRepository;
+    private final FromJsonHelper fromApiJsonHelper;
+    private final SelfServiceRegistrationReadPlatformService selfServiceRegistrationReadPlatformService;
+    private final ClientRepositoryWrapper clientRepository;
+    private final PasswordValidationPolicyRepository passwordValidationPolicy;
+    private final UserDomainService userDomainService;
+    private final GmailBackedPlatformEmailService gmailBackedPlatformEmailService;
+    private final SmsMessageRepository smsMessageRepository;
+    private SmsMessageScheduledJobService smsMessageScheduledJobService;
+    private final SmsCampaignDropdownReadPlatformService smsCampaignDropdownReadPlatformService;
+    private final AppUserReadPlatformService appUserReadPlatformService;
+
+    @Autowired
+    public SelfServiceRegistrationWritePlatformServiceImpl(final SelfServiceRegistrationRepository selfServiceRegistrationRepository,
+            final FromJsonHelper fromApiJsonHelper,
+            final SelfServiceRegistrationReadPlatformService selfServiceRegistrationReadPlatformService,
+            final ClientRepositoryWrapper clientRepository, final PasswordValidationPolicyRepository passwordValidationPolicy,
+            final UserDomainService userDomainService, final GmailBackedPlatformEmailService gmailBackedPlatformEmailService,
+            final SmsMessageRepository smsMessageRepository, SmsMessageScheduledJobService smsMessageScheduledJobService,
+            final SmsCampaignDropdownReadPlatformService smsCampaignDropdownReadPlatformService,
+            final AppUserReadPlatformService appUserReadPlatformService) {
+        this.selfServiceRegistrationRepository = selfServiceRegistrationRepository;
+        this.fromApiJsonHelper = fromApiJsonHelper;
+        this.selfServiceRegistrationReadPlatformService = selfServiceRegistrationReadPlatformService;
+        this.clientRepository = clientRepository;
+        this.passwordValidationPolicy = passwordValidationPolicy;
+        this.userDomainService = userDomainService;
+        this.gmailBackedPlatformEmailService = gmailBackedPlatformEmailService;
+        this.smsMessageRepository = smsMessageRepository;
+        this.smsMessageScheduledJobService = smsMessageScheduledJobService;
+        this.smsCampaignDropdownReadPlatformService = smsCampaignDropdownReadPlatformService;
+        this.appUserReadPlatformService = appUserReadPlatformService;
+    }
+
+    @Override
+    public SelfServiceRegistration createRegistrationRequest(String apiRequestBodyAsJson) {
+        Gson gson = new Gson();
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("user");
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, apiRequestBodyAsJson,
+                SelfServiceApiConstants.REGISTRATION_REQUEST_DATA_PARAMETERS);
+        JsonElement element = gson.fromJson(apiRequestBodyAsJson.toString(), JsonElement.class);
+
+        String accountNumber = this.fromApiJsonHelper.extractStringNamed(SelfServiceApiConstants.accountNumberParamName, element);
+        baseDataValidator.reset().parameter(SelfServiceApiConstants.accountNumberParamName).value(accountNumber).notNull().notBlank()
+                .notExceedingLengthOf(100);
+
+        String firstName = this.fromApiJsonHelper.extractStringNamed(SelfServiceApiConstants.firstNameParamName, element);
+        baseDataValidator.reset().parameter(SelfServiceApiConstants.firstNameParamName).value(firstName).notBlank()
+                .notExceedingLengthOf(100);
+
+        String lastName = this.fromApiJsonHelper.extractStringNamed(SelfServiceApiConstants.lastNameParamName, element);
+        baseDataValidator.reset().parameter(SelfServiceApiConstants.lastNameParamName).value(lastName).notBlank().notExceedingLengthOf(100);
+
+        String username = this.fromApiJsonHelper.extractStringNamed(SelfServiceApiConstants.usernameParamName, element);
+        baseDataValidator.reset().parameter(SelfServiceApiConstants.usernameParamName).value(username).notBlank().notExceedingLengthOf(100);
+
+        // validate password policy
+        String password = this.fromApiJsonHelper.extractStringNamed(SelfServiceApiConstants.passwordParamName, element);
+        final PasswordValidationPolicy validationPolicy = this.passwordValidationPolicy.findActivePasswordValidationPolicy();
+        final String regex = validationPolicy.getRegex();
+        final String description = validationPolicy.getDescription();
+        baseDataValidator.reset().parameter(SelfServiceApiConstants.passwordParamName).value(password)
+                .matchesRegularExpression(regex, description).notExceedingLengthOf(100);
+
+        String authenticationMode = this.fromApiJsonHelper.extractStringNamed(SelfServiceApiConstants.authenticationModeParamName, element);
+        baseDataValidator.reset().parameter(SelfServiceApiConstants.authenticationModeParamName).value(authenticationMode).notBlank()
+                .isOneOfTheseStringValues(SelfServiceApiConstants.emailModeParamName, SelfServiceApiConstants.mobileModeParamName);
+
+        String email = this.fromApiJsonHelper.extractStringNamed(SelfServiceApiConstants.emailParamName, element);
+        baseDataValidator.reset().parameter(SelfServiceApiConstants.emailParamName).value(email).notNull().notBlank()
+                .notExceedingLengthOf(100);
+
+        boolean isEmailAuthenticationMode = authenticationMode.equalsIgnoreCase(SelfServiceApiConstants.emailModeParamName);
+        String mobileNumber = null;
+        if (!isEmailAuthenticationMode) {
+            mobileNumber = this.fromApiJsonHelper.extractStringNamed(SelfServiceApiConstants.mobileNumberParamName, element);
+            baseDataValidator.reset().parameter(SelfServiceApiConstants.mobileNumberParamName).value(mobileNumber).notNull()
+                    .validatePhoneNumber();
+        }
+        validateForDuplicateUsername(username);
+
+        throwExceptionIfValidationError(dataValidationErrors, accountNumber, firstName, lastName, mobileNumber, isEmailAuthenticationMode);
+
+        String authenticationToken = randomAuthorizationTokenGeneration();
+        Client client = this.clientRepository.getClientByAccountNumber(accountNumber);
+        SelfServiceRegistration selfServiceRegistration = SelfServiceRegistration.instance(client, accountNumber, firstName, lastName,
+                mobileNumber, email, authenticationToken, username, password);
+        this.selfServiceRegistrationRepository.saveAndFlush(selfServiceRegistration);
+        sendAuthorizationToken(selfServiceRegistration, isEmailAuthenticationMode);
+        return selfServiceRegistration;
+
+    }
+
+    public void validateForDuplicateUsername(String username) {
+        boolean isDuplicateUserName = this.appUserReadPlatformService.isUsernameExist(username);
+        if (isDuplicateUserName) {
+            final StringBuilder defaultMessageBuilder = new StringBuilder("User with username ").append(username)
+                    .append(" already exists.");
+            throw new PlatformDataIntegrityException("error.msg.user.duplicate.username", defaultMessageBuilder.toString(),
+                    SelfServiceApiConstants.usernameParamName, username);
+        }
+    }
+
+    public void sendAuthorizationToken(SelfServiceRegistration selfServiceRegistration, Boolean isEmailAuthenticationMode) {
+        if (isEmailAuthenticationMode) {
+            sendAuthorizationMail(selfServiceRegistration);
+        } else {
+            sendAuthorizationMessage(selfServiceRegistration);
+        }
+    }
+
+    private void sendAuthorizationMessage(SelfServiceRegistration selfServiceRegistration) {
+        Collection<SmsProviderData> smsProviders = this.smsCampaignDropdownReadPlatformService.retrieveSmsProviders();
+        if (smsProviders.isEmpty()) { throw new PlatformDataIntegrityException("error.msg.mobile.service.provider.not.available",
+                "Mobile service provider not available."); }
+        Long providerId = (new ArrayList<>(smsProviders)).get(0).getId();
+        final String message = "Hi  " + selfServiceRegistration.getFirstName() + "," + "\n"
+                + "To create user, please use following details \n" + "Request Id : " + selfServiceRegistration.getId()
+                + "\n Authentication Token : " + selfServiceRegistration.getAuthenticationToken();
+        String externalId = null;
+        Group group = null;
+        Staff staff = null;
+        SmsCampaign smsCampaign = null;
+        SmsMessage smsMessage = SmsMessage.instance(externalId, group, selfServiceRegistration.getClient(), staff,
+                SmsMessageStatusType.PENDING, message, selfServiceRegistration.getMobileNumber(), smsCampaign);
+        this.smsMessageRepository.save(smsMessage);
+        this.smsMessageScheduledJobService.sendTriggeredMessage(new ArrayList<>(Arrays.asList(smsMessage)), providerId);
+    }
+
+    private void sendAuthorizationMail(SelfServiceRegistration selfServiceRegistration) {
+        final String subject = "Authorization token ";
+        final String body = "Hi  " + selfServiceRegistration.getFirstName() + "," + "\n" + "To create user, please use following details\n"
+                + "Request Id : " + selfServiceRegistration.getId() + "\n Authentication Token : "
+                + selfServiceRegistration.getAuthenticationToken();
+
+        final EmailDetail emailDetail = new EmailDetail(subject, body, selfServiceRegistration.getEmail(),
+                selfServiceRegistration.getFirstName());
+        this.gmailBackedPlatformEmailService.sendDefinedEmail(emailDetail);
+    }
+
+    private void throwExceptionIfValidationError(final List<ApiParameterError> dataValidationErrors, String accountNumber,
+            String firstName, String lastName, String mobileNumber, boolean isEmailAuthenticationMode) {
+        if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+        boolean isClientExist = this.selfServiceRegistrationReadPlatformService.isClientExist(accountNumber, firstName, lastName,
+                mobileNumber, isEmailAuthenticationMode);
+        if (!isClientExist) { throw new ClientNotFoundException(); }
+    }
+
+    public static String randomAuthorizationTokenGeneration() {
+        Integer randomPIN = (int) (Math.random() * 9000) + 1000;
+        return randomPIN.toString();
+    }
+
+    @Override
+    public AppUser createUser(String apiRequestBodyAsJson) {
+        JsonCommand command = null;
+        String username = null;
+        try {
+            Gson gson = new Gson();
+            final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+            final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+            final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("user");
+            this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, apiRequestBodyAsJson,
+                    SelfServiceApiConstants.CREATE_USER_REQUEST_DATA_PARAMETERS);
+            JsonElement element = gson.fromJson(apiRequestBodyAsJson.toString(), JsonElement.class);
+
+            Long id = this.fromApiJsonHelper.extractLongNamed(SelfServiceApiConstants.requestIdParamName, element);
+            baseDataValidator.reset().parameter(SelfServiceApiConstants.requestIdParamName).value(id).notNull().integerGreaterThanZero();
+            command = JsonCommand.fromJsonElement(id, element);
+            String authenticationToken = this.fromApiJsonHelper.extractStringNamed(SelfServiceApiConstants.authenticationTokenParamName,
+                    element);
+            baseDataValidator.reset().parameter(SelfServiceApiConstants.authenticationTokenParamName).value(authenticationToken).notBlank()
+                    .notNull().notExceedingLengthOf(100);
+
+            if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
+
+            SelfServiceRegistration selfServiceRegistration = this.selfServiceRegistrationRepository.getRequestByIdAndAuthenticationToken(
+                    id, authenticationToken);
+            if (selfServiceRegistration == null) { throw new SelfServiceRegistrationNotFoundException(id, authenticationToken); }
+            username = selfServiceRegistration.getUsername();
+            Client client = selfServiceRegistration.getClient();
+            final boolean passwordNeverExpire = true;
+            final boolean isSelfServiceUser = true;
+            final Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
+            authorities.add(new SimpleGrantedAuthority("DUMMY_ROLE_NOT_USED_OR_PERSISTED_TO_AVOID_EXCEPTION"));
+            final Set<Role> allRoles = new HashSet<>();
+            List<Client> clients = new ArrayList<>(Arrays.asList(client));
+            User user = new User(selfServiceRegistration.getUsername(), selfServiceRegistration.getPassword(), authorities);
+            AppUser appUser = new AppUser(client.getOffice(), user, allRoles, selfServiceRegistration.getEmail(), client.getFirstname(),
+                    client.getLastname(), null, passwordNeverExpire, isSelfServiceUser, clients);
+            this.userDomainService.create(appUser, true);
+            return appUser;
+
+        } catch (final DataIntegrityViolationException dve) {
+            handleDataIntegrityIssues(command, dve.getMostSpecificCause(), dve, username);
+            return null;
+        } catch (final PersistenceException | AuthenticationServiceException dve) {
+            Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
+            handleDataIntegrityIssues(command, throwable, dve, username);
+            return null;
+        }
+
+    }
+
+    private void handleDataIntegrityIssues(final JsonCommand command, final Throwable realCause, final Exception dve, String username) {
+        if (realCause.getMessage().contains("'username_org'")) {
+            final StringBuilder defaultMessageBuilder = new StringBuilder("User with username ").append(username)
+                    .append(" already exists.");
+            throw new PlatformDataIntegrityException("error.msg.user.duplicate.username", defaultMessageBuilder.toString(), "username",
+                    username);
+        }
+        throw new PlatformDataIntegrityException("error.msg.unknown.data.integrity.issue", "Unknown data integrity issue with resource.");
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/AppUserReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/AppUserReadPlatformService.java
index 067c725..b17f6a1 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/AppUserReadPlatformService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/AppUserReadPlatformService.java
@@ -31,4 +31,6 @@
     AppUserData retrieveNewUserDetails();
 
     AppUserData retrieveUser(Long userId);
+    
+    boolean isUsernameExist(String username);
 }
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/AppUserReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/AppUserReadPlatformServiceImpl.java
index 21e064f..13cbbd8 100755
--- a/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/AppUserReadPlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/AppUserReadPlatformServiceImpl.java
@@ -212,4 +212,13 @@
                     + " join m_office o on o.id = u.office_id where o.hierarchy like ? and u.is_deleted=0 order by u.username";
         }
     }
+
+    @Override
+    public boolean isUsernameExist(String username) {
+        String sql = "select count(*) from m_appuser where username = ?";
+        Object[] params = new Object[] { username };
+        Integer count = this.jdbcTemplate.queryForObject(sql, params, Integer.class);
+        if (count == 0) { return false; }
+        return true;
+    }
 }
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/META-INF/spring/appContext.xml b/fineract-provider/src/main/resources/META-INF/spring/appContext.xml
index 9fe95d4..dffd1b2 100644
--- a/fineract-provider/src/main/resources/META-INF/spring/appContext.xml
+++ b/fineract-provider/src/main/resources/META-INF/spring/appContext.xml
@@ -55,7 +55,8 @@
 										  org.apache.fineract.template.*,
 										  org.apache.fineract.template.service.*,
 										  org.apache.fineract.useradministration.*,
-										  org.apache.fineract.batch">
+										  org.apache.fineract.batch,
+										  org.apache.fineract.adhocquery.*">
 		<context:exclude-filter expression="org.springframework.stereotype.Controller"
 			type="annotation" />
 
@@ -80,6 +81,7 @@
 	<jpa:repositories base-package="org.apache.fineract.scheduledjobs.domain" />
 	<jpa:repositories base-package="org.apache.fineract.template.domain" />
 	<jpa:repositories base-package="org.apache.fineract.infrastructure.campaigns.sms.domain" />
+	<jpa:repositories base-package="org.apache.fineract.adhocquery.domain" />
 	
 	<import resource="infrastructure.xml" />
 
diff --git a/fineract-provider/src/main/resources/META-INF/spring/securityContext.xml b/fineract-provider/src/main/resources/META-INF/spring/securityContext.xml
index ee4fb6d..f03220f 100644
--- a/fineract-provider/src/main/resources/META-INF/spring/securityContext.xml
+++ b/fineract-provider/src/main/resources/META-INF/spring/securityContext.xml
@@ -38,6 +38,10 @@
 				method="POST" requires-channel="https" />
 			<intercept-url pattern="/api/*/self/authentication" access="permitAll"
 				method="POST" requires-channel="https" />
+			<intercept-url pattern="/api/*/self/registration" access="permitAll"
+				method="POST" requires-channel="https" />
+			<intercept-url pattern="/api/*/self/registration/user" access="permitAll"
+				method="POST" requires-channel="https" />
 			<intercept-url pattern="/api/**" access="isFullyAuthenticated()"
 				method="GET" requires-channel="https" />
 			<intercept-url pattern="/api/**" access="isFullyAuthenticated()"
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V330__savings_account_transaction_releaseId.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V330__savings_account_transaction_releaseId.sql
index cf9e24c..c20ea9f 100644
--- a/fineract-provider/src/main/resources/sql/migrations/core_db/V330__savings_account_transaction_releaseId.sql
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V330__savings_account_transaction_releaseId.sql
@@ -40,5 +40,9 @@
 
 ALTER TABLE `m_savings_account_transaction` ADD COLUMN `release_id_of_hold_amount` BIGINT(20) NULL DEFAULT NULL;
 
+--  modify `m_savings_account` 
+
+ALTER TABLE `m_savings_account` ADD COLUMN `total_savings_amount_on_hold` DECIMAL(19,6) NULL DEFAULT NULL;
+
 
 
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V331__holiday_schema_changes.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V331__holiday_schema_changes.sql
new file mode 100644
index 0000000..94ae19a
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V331__holiday_schema_changes.sql
@@ -0,0 +1,23 @@
+--
+-- 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.
+--
+
+
+ALTER TABLE m_holiday
+MODIFY COLUMN repayments_rescheduled_to DATETIME NULL DEFAULT NULL,
+ADD COLUMN `rescheduling_type` INT(5) NOT NULL DEFAULT '2';
\ No newline at end of file
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V332__self_service_registration_schema.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V332__self_service_registration_schema.sql
new file mode 100644
index 0000000..0f93700
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V332__self_service_registration_schema.sql
@@ -0,0 +1,38 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one
+-- or more contributor license agreements. See the NOTICE file
+-- distributed with this work for additional information
+-- regarding copyright ownership. The ASF licenses this file
+-- to you under the Apache License, Version 2.0 (the
+-- "License"); you may not use this file except in compliance
+-- with the License. You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing,
+-- software distributed under the License is distributed on an
+-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+-- KIND, either express or implied. See the License for the
+-- specific language governing permissions and limitations
+-- under the License.
+--
+
+CREATE TABLE `request_audit_table` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`lastname` VARCHAR(100) NOT NULL,
+	`username` VARCHAR(100) NOT NULL,
+	`mobile_number` VARCHAR(50) NULL DEFAULT NULL,
+	`firstname` VARCHAR(100) NOT NULL,
+	`authentication_token` VARCHAR(100) NULL DEFAULT NULL,
+	`password` VARCHAR(250) NOT NULL,
+	`email` VARCHAR(100) NOT NULL,
+	`client_id` BIGINT(20) NOT NULL,
+	`created_date` DATE NOT NULL,
+	`account_number` VARCHAR(100) NOT NULL,
+	PRIMARY KEY (`id`),
+	INDEX `FK1_request_audit_table_m_client` (`client_id`),
+	CONSTRAINT `FK1_request_audit_table_m_client` FOREIGN KEY (`client_id`) REFERENCES `m_client` (`id`)
+)
+COLLATE='utf8_general_ci'
+ENGINE=InnoDB
+;
diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V333__adhocquery.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V333__adhocquery.sql
new file mode 100644
index 0000000..1da45cf
--- /dev/null
+++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V333__adhocquery.sql
@@ -0,0 +1,49 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one
+-- or more contributor license agreements. See the NOTICE file
+-- distributed with this work for additional information
+-- regarding copyright ownership. The ASF licenses this file
+-- to you under the Apache License, Version 2.0 (the
+-- "License"); you may not use this file except in compliance
+-- with the License. You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing,
+-- software distributed under the License is distributed on an
+-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+-- KIND, either express or implied. See the License for the
+-- specific language governing permissions and limitations
+-- under the License.
+--
+
+CREATE TABLE `m_adhoc` (
+	`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
+	`name` VARCHAR(100) NULL DEFAULT NULL,
+	`query` VARCHAR(2000) NULL DEFAULT NULL,
+	`table_name` VARCHAR(100) NULL DEFAULT NULL,
+	`table_fields` VARCHAR(1000) NULL DEFAULT NULL,
+	`email` VARCHAR(500) NOT NULL,
+	`IsActive` TINYINT(1) NOT NULL DEFAULT '0',
+	`created_date` DATETIME NULL DEFAULT NULL,
+	`createdby_id` BIGINT NOT NULL,
+	`lastmodifiedby_id` BIGINT(20) NOT NULL,
+	`lastmodified_date` DATETIME NULL DEFAULT NULL,
+	PRIMARY KEY (`id`),
+	CONSTRAINT `createdby_id` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`),
+	CONSTRAINT `lastmodifiedby_id` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`)
+)
+COLLATE='latin1_swedish_ci'
+ENGINE=InnoDB
+;
+
+INSERT INTO `m_permission`
+(`grouping`,`code`,`entity_name`,`action_name`,`can_maker_checker`) VALUES
+('authorisation','UPDATE_ADHOC','ADHOC','UPDATE',1),
+('authorisation','UPDATE_ADHOC_CHECKER','ADHOC','UPDATE',0),
+('authorisation','DELETE_ADHOC','ADHOC','DELETE',1),
+('authorisation','DELETE_ADHOC_CHECKER','ADHOC','DELETE',0),
+('authorisation','CREATE_ADHOC','ADHOC','CREATE',1),
+('authorisation','CREATE_ADHOC_CHECKER','ADHOC','CREATE',0);
+
+INSERT INTO `job` (`name`, `display_name`, `cron_expression`, `create_time`) VALUES ('Generate AdhocClient Schedule', 'Generate AdhocClient Schedule', '0 0 12 1/1 * ? *', now());